diff options
Diffstat (limited to 'director.cpp')
-rw-r--r-- | director.cpp | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/director.cpp b/director.cpp new file mode 100644 index 0000000..24335da --- /dev/null +++ b/director.cpp | |||
@@ -0,0 +1,378 @@ | |||
1 | #include "director.h" | ||
2 | #include <dirent.h> | ||
3 | #include <iostream> | ||
4 | |||
5 | extern "C" { | ||
6 | #include <libavformat/avformat.h> | ||
7 | #include <libavcodec/avcodec.h> | ||
8 | #include <libavutil/imgutils.h> | ||
9 | #include <libswscale/swscale.h> | ||
10 | } | ||
11 | |||
12 | namespace ffmpeg { | ||
13 | |||
14 | class format { | ||
15 | public: | ||
16 | |||
17 | format(std::string videoPath) | ||
18 | { | ||
19 | if (avformat_open_input(&ptr_, videoPath.c_str(), nullptr, nullptr)) | ||
20 | { | ||
21 | throw ffmpeg_error("Could not open video " + videoPath); | ||
22 | } | ||
23 | |||
24 | if (avformat_find_stream_info(ptr_, nullptr)) | ||
25 | { | ||
26 | throw ffmpeg_error("Could not read stream"); | ||
27 | } | ||
28 | } | ||
29 | |||
30 | ~format() | ||
31 | { | ||
32 | avformat_close_input(&ptr_); | ||
33 | } | ||
34 | |||
35 | format(const format& other) = delete; | ||
36 | |||
37 | AVFormatContext* ptr() | ||
38 | { | ||
39 | return ptr_; | ||
40 | } | ||
41 | |||
42 | private: | ||
43 | |||
44 | AVFormatContext* ptr_ = nullptr; | ||
45 | }; | ||
46 | |||
47 | class codec { | ||
48 | public: | ||
49 | |||
50 | codec( | ||
51 | format& fmt, | ||
52 | AVStream* st) | ||
53 | { | ||
54 | AVCodec* dec = avcodec_find_decoder(st->codecpar->codec_id); | ||
55 | if (!dec) | ||
56 | { | ||
57 | throw ffmpeg_error("Failed to find codec"); | ||
58 | } | ||
59 | |||
60 | ptr_ = avcodec_alloc_context3(dec); | ||
61 | if (!ptr_) | ||
62 | { | ||
63 | throw ffmpeg_error("Failed to allocate codec context"); | ||
64 | } | ||
65 | |||
66 | if (avcodec_parameters_to_context(ptr_, st->codecpar) < 0) | ||
67 | { | ||
68 | throw ffmpeg_error("Failed to copy codec parameters to decoder"); | ||
69 | } | ||
70 | |||
71 | // Init the decoders, with or without reference counting | ||
72 | AVDictionary* opts = nullptr; | ||
73 | av_dict_set(&opts, "refcounted_frames", "0", 0); | ||
74 | |||
75 | if (avcodec_open2(ptr_, dec, &opts) < 0) | ||
76 | { | ||
77 | throw ffmpeg_error("Failed to open codec"); | ||
78 | } | ||
79 | } | ||
80 | |||
81 | codec(const codec& other) = delete; | ||
82 | |||
83 | ~codec() | ||
84 | { | ||
85 | avcodec_free_context(&ptr_); | ||
86 | } | ||
87 | |||
88 | int getWidth() const | ||
89 | { | ||
90 | return ptr_->width; | ||
91 | } | ||
92 | |||
93 | int getHeight() const | ||
94 | { | ||
95 | return ptr_->height; | ||
96 | } | ||
97 | |||
98 | enum AVPixelFormat getPixelFormat() const | ||
99 | { | ||
100 | return ptr_->pix_fmt; | ||
101 | } | ||
102 | |||
103 | AVCodecContext* ptr() | ||
104 | { | ||
105 | return ptr_; | ||
106 | } | ||
107 | |||
108 | private: | ||
109 | |||
110 | AVCodecContext* ptr_ = nullptr; | ||
111 | }; | ||
112 | |||
113 | class packet { | ||
114 | public: | ||
115 | |||
116 | packet() | ||
117 | { | ||
118 | ptr_ = av_packet_alloc(); | ||
119 | } | ||
120 | |||
121 | ~packet() | ||
122 | { | ||
123 | av_packet_free(&ptr_); | ||
124 | } | ||
125 | |||
126 | packet(const packet& other) = delete; | ||
127 | |||
128 | int getStreamIndex() const | ||
129 | { | ||
130 | return ptr_->stream_index; | ||
131 | } | ||
132 | |||
133 | AVPacket* ptr() | ||
134 | { | ||
135 | return ptr_; | ||
136 | } | ||
137 | |||
138 | int getFlags() const | ||
139 | { | ||
140 | return ptr_->flags; | ||
141 | } | ||
142 | |||
143 | private: | ||
144 | |||
145 | AVPacket* ptr_ = nullptr; | ||
146 | }; | ||
147 | |||
148 | class frame { | ||
149 | public: | ||
150 | |||
151 | frame() | ||
152 | { | ||
153 | ptr_ = av_frame_alloc(); | ||
154 | } | ||
155 | |||
156 | ~frame() | ||
157 | { | ||
158 | av_frame_free(&ptr_); | ||
159 | } | ||
160 | |||
161 | frame(const frame& other) = delete; | ||
162 | |||
163 | uint8_t** getData() | ||
164 | { | ||
165 | return ptr_->data; | ||
166 | } | ||
167 | |||
168 | int* getLinesize() | ||
169 | { | ||
170 | return ptr_->linesize; | ||
171 | } | ||
172 | |||
173 | AVFrame* ptr() | ||
174 | { | ||
175 | return ptr_; | ||
176 | } | ||
177 | |||
178 | private: | ||
179 | |||
180 | AVFrame* ptr_ = nullptr; | ||
181 | }; | ||
182 | |||
183 | class sws { | ||
184 | public: | ||
185 | |||
186 | sws( | ||
187 | int srcW, | ||
188 | int srcH, | ||
189 | enum AVPixelFormat srcFormat, | ||
190 | int dstW, | ||
191 | int dstH, | ||
192 | enum AVPixelFormat dstFormat, | ||
193 | int flags, | ||
194 | SwsFilter* srcFilter, | ||
195 | SwsFilter* dstFilter, | ||
196 | const double* param) | ||
197 | { | ||
198 | ptr_ = sws_getContext( | ||
199 | srcW, | ||
200 | srcH, | ||
201 | srcFormat, | ||
202 | dstW, | ||
203 | dstH, | ||
204 | dstFormat, | ||
205 | flags, | ||
206 | srcFilter, | ||
207 | dstFilter, | ||
208 | param); | ||
209 | |||
210 | if (ptr_ == NULL) | ||
211 | { | ||
212 | throw ffmpeg_error("Could not allocate sws context"); | ||
213 | } | ||
214 | } | ||
215 | |||
216 | ~sws() | ||
217 | { | ||
218 | sws_freeContext(ptr_); | ||
219 | } | ||
220 | |||
221 | sws(const sws& other) = delete; | ||
222 | |||
223 | void scale( | ||
224 | const uint8_t* const srcSlice[], | ||
225 | const int srcStride[], | ||
226 | int srcSliceY, | ||
227 | int srcSliceH, | ||
228 | uint8_t* const dst[], | ||
229 | const int dstStride[]) | ||
230 | { | ||
231 | sws_scale( | ||
232 | ptr_, | ||
233 | srcSlice, | ||
234 | srcStride, | ||
235 | srcSliceY, | ||
236 | srcSliceH, | ||
237 | dst, | ||
238 | dstStride); | ||
239 | } | ||
240 | |||
241 | private: | ||
242 | |||
243 | SwsContext* ptr_; | ||
244 | }; | ||
245 | |||
246 | } | ||
247 | |||
248 | director::director(std::string videosPath) : videosPath_(videosPath) | ||
249 | { | ||
250 | DIR* videodir; | ||
251 | struct dirent* ent; | ||
252 | if ((videodir = opendir(videosPath.c_str())) == nullptr) | ||
253 | { | ||
254 | throw std::invalid_argument("Couldn't find videos"); | ||
255 | } | ||
256 | |||
257 | while ((ent = readdir(videodir)) != nullptr) | ||
258 | { | ||
259 | std::string dname(ent->d_name); | ||
260 | if (dname.find(".mp4") != std::string::npos) | ||
261 | { | ||
262 | videos_.push_back(dname); | ||
263 | } | ||
264 | } | ||
265 | |||
266 | closedir(videodir); | ||
267 | } | ||
268 | |||
269 | Magick::Image director::generate(std::mt19937& rng) const | ||
270 | { | ||
271 | std::uniform_int_distribution<size_t> videoDist(0, videos_.size() - 1); | ||
272 | std::string video = videosPath_ + "/" + videos_[videoDist(rng)]; | ||
273 | |||
274 | ffmpeg::format fmt(video); | ||
275 | |||
276 | int streamIdx = | ||
277 | av_find_best_stream(fmt.ptr(), AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); | ||
278 | |||
279 | if (streamIdx < 0) | ||
280 | { | ||
281 | throw ffmpeg_error("Could not find stream"); | ||
282 | } | ||
283 | |||
284 | AVStream* stream = fmt.ptr()->streams[streamIdx]; | ||
285 | |||
286 | ffmpeg::codec cdc(fmt, stream); | ||
287 | |||
288 | size_t codecw = cdc.getWidth(); | ||
289 | size_t codech = cdc.getHeight(); | ||
290 | |||
291 | std::uniform_int_distribution<int64_t> frameDist(0, stream->duration - 1); | ||
292 | int64_t seek = frameDist(rng); | ||
293 | |||
294 | std::cout << seek << std::endl; | ||
295 | |||
296 | if (av_seek_frame(fmt.ptr(), streamIdx, seek, 0)) | ||
297 | { | ||
298 | throw ffmpeg_error("Could not seek"); | ||
299 | } | ||
300 | |||
301 | ffmpeg::frame frame; | ||
302 | ffmpeg::frame converted; | ||
303 | |||
304 | av_image_alloc( | ||
305 | converted.getData(), | ||
306 | converted.getLinesize(), | ||
307 | codecw, | ||
308 | codech, | ||
309 | AV_PIX_FMT_RGB24, | ||
310 | 1); | ||
311 | |||
312 | ffmpeg::packet pkt; | ||
313 | |||
314 | do | ||
315 | { | ||
316 | if (av_read_frame(fmt.ptr(), pkt.ptr()) < 0) | ||
317 | { | ||
318 | throw ffmpeg_error("Could not read frame"); | ||
319 | } | ||
320 | } while ((pkt.getStreamIndex() != streamIdx) | ||
321 | || !(pkt.getFlags() & AV_PKT_FLAG_KEY)); | ||
322 | |||
323 | int ret; | ||
324 | do { | ||
325 | if (avcodec_send_packet(cdc.ptr(), pkt.ptr()) < 0) | ||
326 | { | ||
327 | throw ffmpeg_error("Could not send packet"); | ||
328 | } | ||
329 | |||
330 | ret = avcodec_receive_frame(cdc.ptr(), frame.ptr()); | ||
331 | } while (ret == AVERROR(EAGAIN)); | ||
332 | |||
333 | if (ret < 0) | ||
334 | { | ||
335 | throw ffmpeg_error("Could not decode frame"); | ||
336 | } | ||
337 | |||
338 | ffmpeg::sws scaler( | ||
339 | cdc.getWidth(), | ||
340 | cdc.getHeight(), | ||
341 | cdc.getPixelFormat(), | ||
342 | cdc.getWidth(), | ||
343 | cdc.getHeight(), | ||
344 | AV_PIX_FMT_RGB24, | ||
345 | 0, nullptr, nullptr, 0); | ||
346 | |||
347 | scaler.scale( | ||
348 | frame.getData(), | ||
349 | frame.getLinesize(), | ||
350 | 0, | ||
351 | codech, | ||
352 | converted.getData(), | ||
353 | converted.getLinesize()); | ||
354 | |||
355 | size_t buffer_size = av_image_get_buffer_size( | ||
356 | AV_PIX_FMT_RGB24, cdc.getWidth(), cdc.getHeight(), 1); | ||
357 | |||
358 | std::vector<uint8_t> buffer(buffer_size); | ||
359 | |||
360 | av_image_copy_to_buffer( | ||
361 | buffer.data(), | ||
362 | buffer_size, | ||
363 | converted.getData(), | ||
364 | converted.getLinesize(), | ||
365 | AV_PIX_FMT_RGB24, | ||
366 | cdc.getWidth(), | ||
367 | cdc.getHeight(), | ||
368 | 1); | ||
369 | |||
370 | size_t width = 1024; | ||
371 | size_t height = codech * width / codecw; | ||
372 | |||
373 | Magick::Image image; | ||
374 | image.read(codecw, codech, "RGB", Magick::CharPixel, buffer.data()); | ||
375 | image.zoom(Magick::Geometry(width, height)); | ||
376 | |||
377 | return image; | ||
378 | } | ||