swaroop: support creation of H264 with 24-bit PCM.
[dcpomatic.git] / src / tools / dcpomatic_ecinema.cc
1 /*
2     Copyright (C) 2018-2019 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 #include "lib/version.h"
22 #include "lib/decrypted_ecinema_kdm.h"
23 #include "lib/config.h"
24 #include "lib/util.h"
25 #include "lib/film.h"
26 #include "lib/dcp_content.h"
27 #include "lib/job_manager.h"
28 #include "lib/cross.h"
29 #include "lib/transcode_job.h"
30 #include "lib/ffmpeg_encoder.h"
31 #include "lib/signal_manager.h"
32 #include <dcp/key.h>
33 extern "C" {
34 #include <libavformat/avformat.h>
35 #include <libavutil/aes_ctr.h>
36 }
37 #include <boost/filesystem.hpp>
38 #include <boost/optional.hpp>
39 #include <openssl/rand.h>
40 #include <getopt.h>
41 #include <sys/stat.h>
42 #include <sys/types.h>
43 #include <string>
44 #include <iostream>
45
46 using std::string;
47 using std::cerr;
48 using std::cout;
49 using std::ofstream;
50 using boost::optional;
51 using boost::shared_ptr;
52
53 static void convert_dcp (
54         boost::filesystem::path input,
55         boost::filesystem::path output_file,
56         boost::optional<boost::filesystem::path> kdm,
57         int crf
58         );
59 static void convert_ffmpeg (boost::filesystem::path input, boost::filesystem::path output_file, string format);
60 static void write_kdm (string id, boost::filesystem::path name, dcp::Key key);
61
62 static void
63 help (string n)
64 {
65         cerr << "Syntax: " << n << " [OPTION] <FILE>|<DIRECTORY>\n"
66              << "  -v, --version        show DCP-o-matic version\n"
67              << "  -h, --help           show this help\n"
68              << "  -o, --output         output directory\n"
69              << "  -f, --format         output format (mov or mp4; defaults to mov)\n"
70              << "  -k, --kdm            DCP KDM filename (if required)\n"
71              << "  -c, --crf            quality (CRF) when transcoding from DCP (0 is best, 51 is worst, defaults to 23)\n"
72              << "\n"
73              << "<FILE> is an unencrypted .mp4 file; <DIRECTORY> is a DCP directory.\n";
74 }
75
76 int
77 main (int argc, char* argv[])
78 {
79         optional<boost::filesystem::path> output;
80         optional<string> format;
81         optional<boost::filesystem::path> kdm;
82         int crf = 23;
83
84         int option_index = 0;
85         while (true) {
86                 static struct option long_options[] = {
87                         { "version", no_argument, 0, 'v' },
88                         { "help", no_argument, 0, 'h' },
89                         { "output", required_argument, 0, 'o' },
90                         { "format", required_argument, 0, 'f' },
91                         { "kdm", required_argument, 0, 'k' },
92                         { "crf", required_argument, 0, 'c' },
93                 };
94
95                 int c = getopt_long (argc, argv, "vho:f:k:c:", long_options, &option_index);
96
97                 if (c == -1) {
98                         break;
99                 }
100
101                 switch (c) {
102                 case 'v':
103                         cout << "dcpomatic version " << dcpomatic_version << " " << dcpomatic_git_commit << "\n";
104                         exit (EXIT_SUCCESS);
105                 case 'h':
106                         help (argv[0]);
107                         exit (EXIT_SUCCESS);
108                 case 'o':
109                         output = optarg;
110                         break;
111                 case 'f':
112                         format = optarg;
113                         break;
114                 case 'k':
115                         kdm = optarg;
116                         break;
117                 case 'c':
118                         crf = atoi(optarg);
119                         break;
120                 }
121         }
122
123         if (optind >= argc) {
124                 help (argv[0]);
125                 exit (EXIT_FAILURE);
126         }
127
128         if (!output) {
129                 cerr << "You must specify --output-media or -o\n";
130                 exit (EXIT_FAILURE);
131         }
132
133         if (!format) {
134                 format = "mov";
135         }
136
137         if (*format != "mov" && *format != "mp4") {
138                 cerr << "Invalid format specified: must be mov or mp4\n";
139                 exit (EXIT_FAILURE);
140         }
141
142         dcpomatic_setup_path_encoding ();
143         dcpomatic_setup ();
144         signal_manager = new SignalManager ();
145
146         boost::filesystem::path input = argv[optind];
147         boost::filesystem::path output_file;
148         if (boost::filesystem::is_directory(input)) {
149                 output_file = *output / (input.parent_path().filename().string() + ".ecinema");
150         } else {
151                 output_file = *output / (input.filename().string() + ".ecinema");
152         }
153
154         if (!boost::filesystem::is_directory(*output)) {
155                 boost::filesystem::create_directory (*output);
156         }
157
158         av_register_all ();
159
160         if (boost::filesystem::is_directory(input)) {
161                 /* Assume input is a DCP */
162                 convert_dcp (input, output_file, kdm, crf);
163         } else {
164                 convert_ffmpeg (input, output_file, *format);
165         }
166 }
167
168 static void
169 convert_ffmpeg (boost::filesystem::path input, boost::filesystem::path output_file, string format)
170 {
171         AVFormatContext* input_fc = avformat_alloc_context ();
172         if (avformat_open_input(&input_fc, input.string().c_str(), 0, 0) < 0) {
173                 cerr << "Could not open input file\n";
174                 exit (EXIT_FAILURE);
175         }
176
177         if (avformat_find_stream_info (input_fc, 0) < 0) {
178                 cerr << "Could not read stream information\n";
179                 exit (EXIT_FAILURE);
180         }
181
182         AVFormatContext* output_fc;
183         avformat_alloc_output_context2 (&output_fc, av_guess_format(format.c_str(), 0, 0), 0, 0);
184
185         for (uint32_t i = 0; i < input_fc->nb_streams; ++i) {
186                 AVStream* is = input_fc->streams[i];
187                 AVStream* os = avformat_new_stream (output_fc, is->codec->codec);
188                 if (avcodec_parameters_copy (os->codecpar, is->codecpar) < 0) {
189                         cerr << "Could not set up output stream.\n";
190                         exit (EXIT_FAILURE);
191                 }
192
193                 os->avg_frame_rate = is->avg_frame_rate;
194
195                 switch (is->codec->codec_type) {
196                 case AVMEDIA_TYPE_VIDEO:
197                         os->time_base = is->time_base;
198                         os->r_frame_rate = is->r_frame_rate;
199                         os->sample_aspect_ratio = is->sample_aspect_ratio;
200                         os->codec->time_base = is->codec->time_base;
201                         os->codec->framerate = is->codec->framerate;
202                         os->codec->pix_fmt = is->codec->pix_fmt;
203                         break;
204                 case AVMEDIA_TYPE_AUDIO:
205                         os->codec->sample_fmt = is->codec->sample_fmt;
206                         os->codec->bits_per_raw_sample = is->codec->bits_per_raw_sample;
207                         os->codec->sample_rate = is->codec->sample_rate;
208                         os->codec->channel_layout = is->codec->channel_layout;
209                         os->codec->channels = is->codec->channels;
210                         if (is->codecpar->codec_id == AV_CODEC_ID_PCM_S24LE) {
211                                 /* XXX: fix incoming 24-bit files labelled lpcm, which apparently isn't allowed */
212                                 os->codecpar->codec_tag = MKTAG('i', 'n', '2', '4');
213                         }
214                         break;
215                 default:
216                         /* XXX */
217                         break;
218                 }
219         }
220
221         if (avio_open2 (&output_fc->pb, output_file.string().c_str(), AVIO_FLAG_WRITE, 0, 0) < 0) {
222                 cerr << "Could not open output file `" << output_file.string() << "'\n";
223                 exit (EXIT_FAILURE);
224         }
225
226         dcp::Key key (AES_CTR_KEY_SIZE);
227         AVDictionary* options = 0;
228         av_dict_set (&options, "encryption_key", key.hex().c_str(), 0);
229         /* XXX: is this OK? */
230         av_dict_set (&options, "encryption_kid", "00000000000000000000000000000000", 0);
231         av_dict_set (&options, "encryption_scheme", "cenc-aes-ctr", 0);
232
233         string id = dcp::make_uuid ();
234         if (av_dict_set(&output_fc->metadata, SWAROOP_ID_TAG, id.c_str(), 0) < 0) {
235                 cerr << "Could not write ID to output.\n";
236                 exit (EXIT_FAILURE);
237         }
238
239         if (avformat_write_header (output_fc, &options) < 0) {
240                 cerr << "Could not write header to output.\n";
241                 exit (EXIT_FAILURE);
242         }
243
244         AVPacket packet;
245         while (av_read_frame(input_fc, &packet) >= 0) {
246                 AVStream* is = input_fc->streams[packet.stream_index];
247                 AVStream* os = output_fc->streams[packet.stream_index];
248                 packet.pts = av_rescale_q_rnd(packet.pts, is->time_base, os->time_base, (AVRounding) (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
249                 packet.dts = av_rescale_q_rnd(packet.dts, is->time_base, os->time_base, (AVRounding) (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
250                 packet.duration = av_rescale_q(packet.duration, is->time_base, os->time_base);
251                 packet.pos = -1;
252                 if (av_interleaved_write_frame (output_fc, &packet) < 0) {
253                         cerr << "Could not write frame to output.\n";
254                         exit (EXIT_FAILURE);
255                 }
256         }
257
258         av_write_trailer (output_fc);
259
260         avformat_free_context (input_fc);
261         avformat_free_context (output_fc);
262
263         write_kdm (id, output_file, key);
264 }
265
266 static void
267 write_kdm (string id, boost::filesystem::path name, dcp::Key key)
268 {
269         DecryptedECinemaKDM decrypted_kdm (id, name.filename().string(), key, optional<dcp::LocalTime>(), optional<dcp::LocalTime>());
270         EncryptedECinemaKDM encrypted_kdm = decrypted_kdm.encrypt (Config::instance()->decryption_chain()->leaf());
271
272         ofstream f(string(name.string() + ".xml").c_str());
273         f << encrypted_kdm.as_xml() << "\n";
274 }
275
276 static void
277 convert_dcp (
278         boost::filesystem::path input, boost::filesystem::path output_file, optional<boost::filesystem::path> kdm, int crf
279         )
280 {
281         shared_ptr<Film> film (new Film(boost::optional<boost::filesystem::path>()));
282         shared_ptr<DCPContent> dcp (new DCPContent(input));
283         film->examine_and_add_content (dcp);
284         if (kdm) {
285                 dcp->add_kdm (dcp::EncryptedKDM(dcp::file_to_string(*kdm)));
286         }
287
288         JobManager* jm = JobManager::instance ();
289         while (jm->work_to_do ()) {
290                 while (signal_manager->ui_idle ()) {}
291                 dcpomatic_sleep (1);
292         }
293         DCPOMATIC_ASSERT (!jm->errors());
294
295         string id = dcp::make_uuid ();
296         dcp::Key key (AES_CTR_KEY_SIZE);
297
298         shared_ptr<TranscodeJob> job (new TranscodeJob(film));
299         job->set_encoder (
300                 shared_ptr<FFmpegEncoder>(
301                         new FFmpegEncoder(film, job, output_file, EXPORT_FORMAT_H264_PCM, false, false, crf, key, id)
302                         )
303                 );
304         jm->add (job);
305         show_jobs_on_console (true);
306
307         write_kdm (id, output_file, key);
308 }