Rename j2k_bandwidth -> video_bit_rate.
[dcpomatic.git] / src / lib / create_cli.cc
1 /*
2     Copyright (C) 2019-2022 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic 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     DCP-o-matic 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 DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 #include "compose.hpp"
23 #include "config.h"
24 #include "create_cli.h"
25 #include "dcp_content_type.h"
26 #include "dcpomatic_log.h"
27 #include "film.h"
28 #include "ratio.h"
29 #include "variant.h"
30 #include <dcp/raw_convert.h>
31 #include <iostream>
32 #include <string>
33
34
35 using std::cout;
36 using std::make_shared;
37 using std::shared_ptr;
38 using std::string;
39 using std::vector;
40 using boost::optional;
41
42
43 string CreateCLI::_help =
44         string("\nSyntax: %1 [OPTION] <CONTENT> [OPTION] [<CONTENT> ...]\n") +
45         variant::insert_dcpomatic("  -v, --version                 show %1 version\n") +
46         "  -h, --help                    show this help\n"
47         "  -n, --name <name>             film name\n"
48         "  -t, --template <name>         template name\n"
49         "      --no-encrypt              make an unencrypted DCP\n"
50         "  -e, --encrypt                 make an encrypted DCP\n"
51         "  -c, --dcp-content-type <type> FTR, SHR, TLR, TST, XSN, RTG, TSR, POL, PSA or ADV\n"
52         "  -f, --dcp-frame-rate <rate>   set DCP video frame rate (otherwise guessed from content)\n"
53         "      --container-ratio <ratio> 119, 133, 137, 138, 166, 178, 185 or 239\n"
54         "  -s, --still-length <n>        number of seconds that still content should last\n"
55         "      --standard <standard>     SMPTE or interop (default SMPTE)\n"
56         "      --no-use-isdcf-name       do not use an ISDCF name; use the specified name unmodified\n"
57         "      --config <dir>            directory containing config.xml and cinemas.xml\n"
58         "      --twok                    make a 2K DCP instead of choosing a resolution based on the content\n"
59         "      --fourk                   make a 4K DCP instead of choosing a resolution based on the content\n"
60         "  -o, --output <dir>            output directory\n"
61         "      --twod                    make a 2D DCP\n"
62         "      --threed                  make a 3D DCP\n"
63         "      --j2k-bandwidth <Mbit/s>  J2K bandwidth in Mbit/s\n"
64         "      --left-eye                next piece of content is for the left eye\n"
65         "      --right-eye               next piece of content is for the right eye\n"
66         "      --channel <channel>       next piece of content should be mapped to audio channel L, R, C, Lfe, Ls, Rs, BsL, BsR, HI, VI\n"
67         "      --gain                    next piece of content should have the given audio gain (in dB)\n"
68         "      --cpl <id>                CPL ID to use from the next piece of content (which is a DCP)\n"
69         "      --kdm <file>              KDM for next piece of content\n";
70
71
72 template <class T>
73 void
74 argument_option (int& n, int argc, char* argv[], string short_name, string long_name, bool* claimed, optional<string>* error, T* out)
75 {
76         string const a = argv[n];
77         if (a != short_name && a != long_name) {
78                 return;
79         }
80
81         if ((n + 1) >= argc) {
82                 **error = String::compose("%1: option %2 requires an argument", argv[0], long_name);
83                 return;
84         }
85
86         *out = dcp::raw_convert<T>(string(argv[++n]));
87         *claimed = true;
88 }
89
90
91 template <class T>
92 void
93 argument_option (
94         int& n,
95         int argc,
96         char* argv[],
97         string short_name,
98         string long_name,
99         bool* claimed,
100         optional<string>* error,
101         boost::optional<T>* out,
102         std::function<boost::optional<T> (string s)> convert = [](string s) { return dcp::raw_convert<T>(s); }
103         )
104 {
105         string const a = argv[n];
106         if (a != short_name && a != long_name) {
107                 return;
108         }
109
110         if ((n + 1) >= argc) {
111                 **error = String::compose("%1: option %2 requires an argument", argv[0], long_name);
112                 return;
113         }
114
115         auto const arg = argv[++n];
116         auto const value = convert(arg);
117         if (!value) {
118                 *error = String::compose("%1: %2 is not valid for %3", argv[0], arg, long_name);
119                 *claimed = true;
120                 return;
121         }
122
123         *out = value;
124         *claimed = true;
125 }
126
127
128 CreateCLI::CreateCLI (int argc, char* argv[])
129         : version (false)
130 {
131         optional<string> dcp_content_type_string;
132         string container_ratio_string;
133         optional<string> standard_string;
134         int dcp_frame_rate_int = 0;
135         string template_name_string;
136         int64_t video_bit_rate_int = 0;
137         auto next_frame_type = VideoFrameType::TWO_D;
138         optional<dcp::Channel> channel;
139         optional<float> gain;
140         optional<boost::filesystem::path> kdm;
141         optional<string> cpl;
142
143         int i = 1;
144         while (i < argc) {
145                 string const a = argv[i];
146                 bool claimed = false;
147
148                 if (a == "-v" || a == "--version") {
149                         version = true;
150                         return;
151                 } else if (a == "-h" || a == "--help") {
152                         error = "Create a film directory (ready for making a DCP) or metadata file from some content files.\n"
153                                 "A film directory will be created if -o or --output is specified, otherwise a metadata file\n"
154                                 "will be written to stdout.\n" + String::compose(_help, argv[0]);
155                         return;
156                 }
157
158                 if (a == "--no-encrypt") {
159                         _no_encrypt = claimed = true;
160                 } else if (a == "-e" || a == "--encrypt") {
161                         _encrypt = claimed = true;
162                 } else if (a == "--no-use-isdcf-name") {
163                         _no_use_isdcf_name = claimed = true;
164                 } else if (a == "--twod") {
165                         _twod = claimed = true;
166                 } else if (a == "--threed") {
167                         _threed = claimed = true;
168                 } else if (a == "--left-eye") {
169                         next_frame_type = VideoFrameType::THREE_D_LEFT;
170                         claimed = true;
171                 } else if (a == "--right-eye") {
172                         next_frame_type = VideoFrameType::THREE_D_RIGHT;
173                         claimed = true;
174                 } else if (a == "--twok") {
175                         _twok = true;
176                         claimed = true;
177                 } else if (a == "--fourk") {
178                         _fourk = true;
179                         claimed = true;
180                 }
181
182                 std::function<optional<string> (string s)> string_to_string = [](string s) {
183                         return s;
184                 };
185
186                 std::function<optional<boost::filesystem::path> (string s)> string_to_path = [](string s) {
187                         return boost::filesystem::path(s);
188                 };
189
190                 std::function<optional<int> (string s)> string_to_nonzero_int = [](string s) {
191                         auto const value = dcp::raw_convert<int>(s);
192                         if (value == 0) {
193                                 return boost::optional<int>();
194                         }
195                         return boost::optional<int>(value);
196                 };
197
198                 argument_option(i, argc, argv, "-n", "--name",             &claimed, &error, &_name);
199                 argument_option(i, argc, argv, "-t", "--template",         &claimed, &error, &template_name_string);
200                 /* See comment below about --cpl */
201                 argument_option(i, argc, argv, "-c", "--dcp-content-type", &claimed, &error, &dcp_content_type_string, string_to_string);
202                 argument_option(i, argc, argv, "-f", "--dcp-frame-rate",   &claimed, &error, &dcp_frame_rate_int);
203                 argument_option(i, argc, argv, "",   "--container-ratio",  &claimed, &error, &container_ratio_string);
204                 argument_option(i, argc, argv, "-s", "--still-length",     &claimed, &error, &still_length, string_to_nonzero_int);
205                 /* See comment below about --cpl */
206                 argument_option(i, argc, argv, "",   "--standard",         &claimed, &error, &standard_string, string_to_string);
207                 argument_option(i, argc, argv, "",   "--config",           &claimed, &error, &config_dir, string_to_path);
208                 argument_option(i, argc, argv, "-o", "--output",           &claimed, &error, &output_dir, string_to_path);
209                 argument_option(i, argc, argv, "",   "--video-bit-rate",   &claimed, &error, &video_bit_rate_int);
210
211                 std::function<optional<dcp::Channel> (string)> convert_channel = [](string channel) -> optional<dcp::Channel>{
212                         if (channel == "L") {
213                                 return dcp::Channel::LEFT;
214                         } else if (channel == "R") {
215                                 return dcp::Channel::RIGHT;
216                         } else if (channel == "C") {
217                                 return dcp::Channel::CENTRE;
218                         } else if (channel == "Lfe") {
219                                 return dcp::Channel::LFE;
220                         } else if (channel == "Ls") {
221                                 return dcp::Channel::LS;
222                         } else if (channel == "Rs") {
223                                 return dcp::Channel::RS;
224                         } else if (channel == "BsL") {
225                                 return dcp::Channel::BSL;
226                         } else if (channel == "BsR") {
227                                 return dcp::Channel::BSR;
228                         } else if (channel == "HI") {
229                                 return dcp::Channel::HI;
230                         } else if (channel == "VI") {
231                                 return dcp::Channel::VI;
232                         } else {
233                                 return {};
234                         }
235                 };
236
237                 argument_option(i, argc, argv, "", "--channel", &claimed, &error, &channel, convert_channel);
238                 argument_option(i, argc, argv, "", "--gain", &claimed, &error, &gain);
239                 argument_option(i, argc, argv, "", "--kdm", &claimed, &error, &kdm, string_to_path);
240                 /* It shouldn't be necessary to use this string_to_string here, but using the other argument_option()
241                  * causes an odd compile error on Ubuntu 18.04.
242                  */
243                 argument_option(i, argc, argv, "", "--cpl", &claimed, &error, &cpl, string_to_string);
244
245                 if (!claimed) {
246                         if (a.length() > 2 && a.substr(0, 2) == "--") {
247                                 error = String::compose("%1: unrecognised option '%2'", argv[0], a) + String::compose(_help, argv[0]);
248                                 return;
249                         } else {
250                                 Content c;
251                                 c.path = a;
252                                 c.frame_type = next_frame_type;
253                                 c.channel = channel;
254                                 c.gain = gain;
255                                 c.kdm = kdm;
256                                 c.cpl = cpl;
257                                 content.push_back (c);
258                                 next_frame_type = VideoFrameType::TWO_D;
259                                 channel = {};
260                                 gain = {};
261                         }
262                 }
263
264                 ++i;
265         }
266
267         if (!template_name_string.empty()) {
268                 _template_name = template_name_string;
269         }
270
271         if (dcp_frame_rate_int) {
272                 dcp_frame_rate = dcp_frame_rate_int;
273         }
274
275         if (video_bit_rate_int) {
276                 _video_bit_rate = video_bit_rate_int * 1000000;
277         }
278
279         if (dcp_content_type_string) {
280                 _dcp_content_type = DCPContentType::from_isdcf_name(*dcp_content_type_string);
281                 if (!_dcp_content_type) {
282                         error = String::compose("%1: unrecognised DCP content type '%2'", argv[0], *dcp_content_type_string);
283                         return;
284                 }
285         }
286
287         if (!container_ratio_string.empty()) {
288                 _container_ratio = Ratio::from_id (container_ratio_string);
289                 if (!_container_ratio) {
290                         error = String::compose("%1: unrecognised container ratio %2", argv[0], container_ratio_string);
291                         return;
292                 }
293         }
294
295         if (standard_string) {
296                 if (*standard_string == "interop") {
297                         _standard = dcp::Standard::INTEROP;
298                 } else if (*standard_string == "SMPTE") {
299                         _standard = dcp::Standard::SMPTE;
300                 } else {
301                         error = String::compose("%1: standard must be SMPTE or interop", argv[0]);
302                         return;
303                 }
304         }
305
306         if (_twod && _threed) {
307                 error = String::compose("%1: specify one of --twod or --threed, not both", argv[0]);
308         }
309
310         if (_no_encrypt && _encrypt) {
311                 error = String::compose("%1: specify one of --no-encrypt or --encrypt, not both", argv[0]);
312         }
313
314         if (content.empty()) {
315                 error = String::compose("%1: no content specified", argv[0]);
316                 return;
317         }
318
319         if (_name.empty()) {
320                 _name = content[0].path.filename().string();
321         }
322
323         if (_video_bit_rate && (*_video_bit_rate < 10000000 || *_video_bit_rate > Config::instance()->maximum_video_bit_rate())) {
324                 error = String::compose("%1: video-bit-rate must be between 10 and %2 Mbit/s", argv[0], (Config::instance()->maximum_video_bit_rate() / 1000000));
325                 return;
326         }
327 }
328
329
330 shared_ptr<Film>
331 CreateCLI::make_film() const
332 {
333         auto film = std::make_shared<Film>(output_dir);
334         dcpomatic_log = film->log();
335         dcpomatic_log->set_types(Config::instance()->log_types());
336         if (_template_name) {
337                 film->use_template(_template_name.get());
338         } else {
339                 /* No template: apply our own CLI tool defaults to override the ones in Config.
340                  * Maybe one day there will be no defaults in Config any more (as they'll be in
341                  * a default template) and we can decide whether to use the default template
342                  * or not.
343                  */
344                 film->set_interop(false);
345                 film->set_dcp_content_type(DCPContentType::from_isdcf_name("TST"));
346         }
347         film->set_name(_name);
348
349         if (_container_ratio) {
350                 film->set_container(_container_ratio);
351         }
352         if (_dcp_content_type) {
353                 film->set_dcp_content_type(_dcp_content_type);
354         }
355         if (_standard) {
356                 film->set_interop(*_standard == dcp::Standard::INTEROP);
357         }
358         film->set_use_isdcf_name(!_no_use_isdcf_name);
359         if (_no_encrypt) {
360                 film->set_encrypted(false);
361         } else if (_encrypt) {
362                 film->set_encrypted(true);
363         }
364         if (_twod) {
365                 film->set_three_d(false);
366         } else if (_threed) {
367                 film->set_three_d(true);
368         }
369         if (_twok) {
370                 film->set_resolution(Resolution::TWO_K);
371         }
372         if (_fourk) {
373                 film->set_resolution(Resolution::FOUR_K);
374         }
375         if (_video_bit_rate) {
376                 film->set_video_bit_rate(*_video_bit_rate);
377         }
378
379         int channels = 6;
380         for (auto cli_content: content) {
381                 if (cli_content.channel) {
382                         channels = std::max(channels, static_cast<int>(*cli_content.channel) + 1);
383                 }
384         }
385         if (channels % 2) {
386                 ++channels;
387         }
388
389         film->set_audio_channels(channels);
390
391         return film;
392 }
393