Add dcpdumpimage tool.
[libdcp.git] / tools / dcpdumpimage.cc
1 /*
2     Copyright (C) 2023 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
35 #include "colour_conversion.h"
36 #include "cpl.h"
37 #include "dcp.h"
38 #include "mono_picture_asset.h"
39 #include "openjpeg_image.h"
40 #include "reel_picture_asset.h"
41 #include "raw_convert.h"
42 #include "reel.h"
43 #include "rgb_xyz.h"
44 #include "util.h"
45 #include "warnings.h"
46 LIBDCP_DISABLE_WARNINGS
47 #include <Magick++.h>
48 LIBDCP_ENABLE_WARNINGS
49 #include <boost/filesystem.hpp>
50 #include <boost/optional.hpp>
51 #include <getopt.h>
52 #include <iostream>
53 #include <string>
54 #include <vector>
55
56
57 using std::cerr;
58 using std::dynamic_pointer_cast;
59 using std::exception;
60 using std::string;
61 using std::vector;
62 using boost::optional;
63
64
65
66 static void
67 help(string n)
68 {
69         cerr << "Syntax: " << n << " [OPTION] <DCP>\n"
70              << "  -h, --help                 show this help\n"
71              << "  -f, --frame-index <index>  frame index (from 0) to extract\n"
72              << "  --horizontal-line <y>      drop a horizontal line over the image at the given position (origin is the top of the frame)\n"
73              << "  -o, --output <filename>    output PNG file\n";
74 }
75
76
77 int
78 main(int argc, char* argv[])
79 {
80         dcp::init();
81         Magick::InitializeMagick(nullptr);
82
83         int frame_index = 0;
84         vector<int> horizontal_lines;
85         optional<boost::filesystem::path> output_filename;
86
87         int option_index = 0;
88         while (true) {
89                 static struct option long_options[] = {
90                         { "help", no_argument, 0, 'h' },
91                         { "frame-index", required_argument, 0, 'f' },
92                         { "horizontal-line", required_argument, 0, 'A' },
93                         { "output", required_argument, 0, 'o' },
94                         { 0, 0, 0, 0 }
95                 };
96
97                 int c = getopt_long(argc, argv, "hf:o:", long_options, &option_index);
98
99                 if (c == -1) {
100                         break;
101                 }
102
103                 switch (c) {
104                 case 'h':
105                         help(argv[0]);
106                         exit (EXIT_SUCCESS);
107                 case 'f':
108                         frame_index = dcp::raw_convert<int>(optarg);
109                         break;
110                 case 'A':
111                         horizontal_lines.push_back(dcp::raw_convert<int>(optarg));
112                         break;
113                 case 'o':
114                         output_filename = optarg;
115                         break;
116                 }
117         }
118
119         if (argc <= optind || argc > (optind + 1)) {
120                 help(argv[0]);
121                 exit(EXIT_FAILURE);
122         }
123
124         if (!output_filename) {
125                 std::cerr << "You must specify -o or --output\n";
126                 exit(EXIT_FAILURE);
127         }
128
129         dcp::DCP dcp(argv[optind]);
130
131         try {
132                 dcp.read();
133         } catch (exception& e) {
134                 std::cerr << e.what() << "\n";
135                 exit(EXIT_FAILURE);
136         }
137
138         if (dcp.cpls().empty()) {
139                 std::cerr << "No CPLs found in DCP.\n";
140                 exit(EXIT_FAILURE);
141         }
142
143         if (dcp.cpls().size() > 1) {
144                 std::cerr << "More than one CPLs found in DCP.\n";
145                 exit(EXIT_FAILURE);
146         }
147
148         bool found = false;
149         auto cpl = dcp.cpls()[0];
150         for (auto reel: cpl->reels()) {
151                 auto duration = reel->main_picture()->actual_duration();
152                 if (frame_index >= duration) {
153                         frame_index -= duration;
154                 } else {
155                         auto reader = dynamic_pointer_cast<dcp::MonoPictureAsset>(reel->main_picture()->asset())->start_read();
156                         auto frame = reader->get_frame(frame_index);
157                         auto xyz = frame->xyz_image();
158                         std::vector<uint8_t> rgba(xyz->size().width * xyz->size().height * 4);
159                         dcp::xyz_to_rgba(xyz, dcp::ColourConversion::srgb_to_xyz(), rgba.data(), xyz->size().width * 4);
160
161                         Magick::Image image(xyz->size().width, xyz->size().height, "BGRA", Magick::CharPixel, rgba.data());
162
163                         image.strokeColor("white");
164                         image.strokeWidth(1);
165                         for (auto line: horizontal_lines) {
166                                 image.draw(Magick::DrawableLine(0, line, xyz->size().width, line));
167                         }
168
169                         image.write(output_filename->string());
170
171                         found = true;
172                 }
173         }
174
175         if (!found) {
176                 std::cerr << "Frame index " << frame_index << " is beyond the end of th DCP.\n";
177                 exit(EXIT_FAILURE);
178         }
179
180         return 0;
181 }