/src/mpv/player/screenshot.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * This file is part of mpv. |
3 | | * |
4 | | * mpv is free software; you can redistribute it and/or |
5 | | * modify it under the terms of the GNU Lesser General Public |
6 | | * License as published by the Free Software Foundation; either |
7 | | * version 2.1 of the License, or (at your option) any later version. |
8 | | * |
9 | | * mpv 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 Lesser General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU Lesser General Public |
15 | | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | |
18 | | #include <stdlib.h> |
19 | | #include <string.h> |
20 | | #include <time.h> |
21 | | |
22 | | #include <libavcodec/avcodec.h> |
23 | | |
24 | | #include "osdep/io.h" |
25 | | |
26 | | #include "mpv_talloc.h" |
27 | | #include "screenshot.h" |
28 | | #include "core.h" |
29 | | #include "command.h" |
30 | | #include "input/cmd.h" |
31 | | #include "misc/bstr.h" |
32 | | #include "misc/dispatch.h" |
33 | | #include "misc/node.h" |
34 | | #include "misc/thread_tools.h" |
35 | | #include "common/msg.h" |
36 | | #include "options/path.h" |
37 | | #include "video/mp_image.h" |
38 | | #include "video/mp_image_pool.h" |
39 | | #include "video/out/vo.h" |
40 | | #include "video/image_writer.h" |
41 | | #include "video/sws_utils.h" |
42 | | #include "sub/osd.h" |
43 | | |
44 | | #include "video/csputils.h" |
45 | | |
46 | 0 | #define MODE_FULL_WINDOW 1 |
47 | 0 | #define MODE_SUBTITLES 2 |
48 | | |
49 | | typedef struct screenshot_ctx { |
50 | | struct MPContext *mpctx; |
51 | | struct mp_log *log; |
52 | | |
53 | | // Command to repeat in each-frame mode. |
54 | | struct mp_cmd *each_frame; |
55 | | |
56 | | int frameno; |
57 | | uint64_t last_frame_count; |
58 | | } screenshot_ctx; |
59 | | |
60 | | void screenshot_init(struct MPContext *mpctx) |
61 | 178k | { |
62 | 178k | mpctx->screenshot_ctx = talloc(mpctx, screenshot_ctx); |
63 | 178k | *mpctx->screenshot_ctx = (screenshot_ctx) { |
64 | 178k | .mpctx = mpctx, |
65 | 178k | .frameno = 1, |
66 | 178k | .log = mp_log_new(mpctx, mpctx->log, "screenshot") |
67 | 178k | }; |
68 | 178k | } |
69 | | |
70 | | static char *stripext(void *talloc_ctx, const char *s) |
71 | 0 | { |
72 | 0 | const char *end = strrchr(s, '.'); |
73 | 0 | if (!end) |
74 | 0 | end = s + strlen(s); |
75 | 0 | return talloc_asprintf(talloc_ctx, "%.*s", (int)(end - s), s); |
76 | 0 | } |
77 | | |
78 | | static bool write_screenshot(struct mp_cmd_ctx *cmd, struct mp_image *img, |
79 | | const char *filename, struct image_writer_opts *opts, |
80 | | bool overwrite) |
81 | 0 | { |
82 | 0 | struct MPContext *mpctx = cmd->mpctx; |
83 | 0 | struct image_writer_opts *gopts = mpctx->opts->screenshot_image_opts; |
84 | 0 | struct image_writer_opts opts_copy = opts ? *opts : *gopts; |
85 | |
|
86 | 0 | mp_cmd_msg(cmd, MSGL_V, "Starting screenshot: '%s'", filename); |
87 | |
|
88 | 0 | mp_core_unlock(mpctx); |
89 | |
|
90 | 0 | bool ok = img && write_image(img, &opts_copy, filename, mpctx->global, |
91 | 0 | mpctx->screenshot_ctx->log, overwrite); |
92 | |
|
93 | 0 | mp_core_lock(mpctx); |
94 | |
|
95 | 0 | if (ok) { |
96 | 0 | mp_cmd_msg(cmd, MSGL_INFO, "Screenshot: '%s'", filename); |
97 | 0 | } else { |
98 | 0 | mp_cmd_msg(cmd, MSGL_ERR, "Error writing screenshot!"); |
99 | 0 | } |
100 | 0 | return ok; |
101 | 0 | } |
102 | | |
103 | | #ifdef _WIN32 |
104 | | #define ILLEGAL_FILENAME_CHARS "?\"/\\<>*|:" |
105 | | #else |
106 | 0 | #define ILLEGAL_FILENAME_CHARS "/" |
107 | | #endif |
108 | | |
109 | | // Replace all characters disallowed in filenames with '_' and return the newly |
110 | | // allocated result string. |
111 | | static char *sanitize_filename(void *talloc_ctx, const char *s) |
112 | 0 | { |
113 | 0 | char *res = talloc_strdup(talloc_ctx, s); |
114 | 0 | char *cur = res; |
115 | 0 | while (*cur) { |
116 | 0 | if (strchr(ILLEGAL_FILENAME_CHARS, *cur) || ((unsigned char)*cur) < 32) |
117 | 0 | *cur = '_'; |
118 | 0 | cur++; |
119 | 0 | } |
120 | 0 | return res; |
121 | 0 | } |
122 | | |
123 | | static void append_filename(char **s, const char *f) |
124 | 0 | { |
125 | 0 | char *append = sanitize_filename(NULL, f); |
126 | 0 | *s = talloc_strdup_append(*s, append); |
127 | 0 | talloc_free(append); |
128 | 0 | } |
129 | | |
130 | | static char *create_fname(struct MPContext *mpctx, char *template, |
131 | | const char *file_ext, int *sequence, int *frameno) |
132 | 0 | { |
133 | 0 | char *res = talloc_strdup(NULL, ""); //empty string, non-NULL context |
134 | |
|
135 | 0 | time_t raw_time = time(NULL); |
136 | 0 | struct tm *local_time = localtime(&raw_time); |
137 | |
|
138 | 0 | if (!template || *template == '\0') |
139 | 0 | return NULL; |
140 | | |
141 | 0 | for (;;) { |
142 | 0 | char *next = strchr(template, '%'); |
143 | 0 | if (!next) |
144 | 0 | break; |
145 | 0 | res = talloc_strndup_append(res, template, next - template); |
146 | 0 | template = next + 1; |
147 | 0 | char fmt = *template++; |
148 | 0 | switch (fmt) { |
149 | 0 | case '#': |
150 | 0 | case '0': |
151 | 0 | case 'n': { |
152 | 0 | int digits = '4'; |
153 | 0 | if (fmt == '#') { |
154 | 0 | if (!*sequence) { |
155 | 0 | *frameno = 1; |
156 | 0 | } |
157 | 0 | fmt = *template++; |
158 | 0 | } |
159 | 0 | if (fmt == '0') { |
160 | 0 | digits = *template++; |
161 | 0 | if (digits < '0' || digits > '9') |
162 | 0 | goto error_exit; |
163 | 0 | fmt = *template++; |
164 | 0 | } |
165 | 0 | if (fmt != 'n') |
166 | 0 | goto error_exit; |
167 | 0 | char fmtstr[] = {'%', '0', digits, 'd', '\0'}; |
168 | 0 | res = talloc_asprintf_append(res, fmtstr, *frameno); |
169 | 0 | if (*frameno < INT_MAX - 1) { |
170 | 0 | (*frameno) += 1; |
171 | 0 | (*sequence) += 1; |
172 | 0 | } |
173 | 0 | break; |
174 | 0 | } |
175 | 0 | case 'f': |
176 | 0 | case 'F': { |
177 | 0 | char *video_file = NULL; |
178 | 0 | if (mpctx->filename) |
179 | 0 | video_file = mp_basename(mpctx->filename); |
180 | |
|
181 | 0 | if (!video_file) |
182 | 0 | video_file = "NO_FILE"; |
183 | |
|
184 | 0 | char *name = video_file; |
185 | 0 | if (fmt == 'F') |
186 | 0 | name = stripext(res, video_file); |
187 | 0 | append_filename(&res, name); |
188 | 0 | break; |
189 | 0 | } |
190 | 0 | case 'x': |
191 | 0 | case 'X': { |
192 | 0 | char *fallback = ""; |
193 | 0 | if (fmt == 'X') { |
194 | 0 | if (template[0] != '{') |
195 | 0 | goto error_exit; |
196 | 0 | char *end = strchr(template, '}'); |
197 | 0 | if (!end) |
198 | 0 | goto error_exit; |
199 | 0 | fallback = talloc_strndup(res, template + 1, end - template - 1); |
200 | 0 | template = end + 1; |
201 | 0 | } |
202 | 0 | if (!mpctx->filename || mp_is_url(bstr0(mpctx->filename))) { |
203 | 0 | res = talloc_strdup_append(res, fallback); |
204 | 0 | } else { |
205 | 0 | bstr dir = mp_dirname(mpctx->filename); |
206 | 0 | if (!bstr_equals0(dir, ".")) |
207 | 0 | res = talloc_asprintf_append(res, "%.*s", BSTR_P(dir)); |
208 | 0 | } |
209 | 0 | break; |
210 | 0 | } |
211 | 0 | case 'p': |
212 | 0 | case 'P': { |
213 | 0 | char *t = mp_format_time(get_playback_time(mpctx), fmt == 'P'); |
214 | 0 | append_filename(&res, t); |
215 | 0 | talloc_free(t); |
216 | 0 | break; |
217 | 0 | } |
218 | 0 | case 'w': { |
219 | 0 | char tfmt = *template; |
220 | 0 | if (!tfmt) |
221 | 0 | goto error_exit; |
222 | 0 | template++; |
223 | 0 | char fmtstr[] = {'%', tfmt, '\0'}; |
224 | 0 | char *s = mp_format_time_fmt(fmtstr, get_playback_time(mpctx)); |
225 | 0 | if (!s) |
226 | 0 | goto error_exit; |
227 | 0 | append_filename(&res, s); |
228 | 0 | talloc_free(s); |
229 | 0 | break; |
230 | 0 | } |
231 | 0 | case 't': { |
232 | 0 | char tfmt = *template; |
233 | 0 | if (!tfmt) |
234 | 0 | goto error_exit; |
235 | 0 | template++; |
236 | 0 | char fmtstr[] = {'%', tfmt, '\0'}; |
237 | 0 | char buffer[80]; |
238 | 0 | if (strftime(buffer, sizeof(buffer), fmtstr, local_time) == 0) |
239 | 0 | buffer[0] = '\0'; |
240 | 0 | append_filename(&res, buffer); |
241 | 0 | break; |
242 | 0 | } |
243 | 0 | case '{': { |
244 | 0 | char *end = strchr(template, '}'); |
245 | 0 | if (!end) |
246 | 0 | goto error_exit; |
247 | 0 | struct bstr prop = bstr_splice(bstr0(template), 0, end - template); |
248 | 0 | char *tmp = talloc_asprintf(NULL, "${%.*s}", BSTR_P(prop)); |
249 | 0 | char *s = mp_property_expand_string(mpctx, tmp); |
250 | 0 | talloc_free(tmp); |
251 | 0 | if (s) |
252 | 0 | append_filename(&res, s); |
253 | 0 | talloc_free(s); |
254 | 0 | template = end + 1; |
255 | 0 | break; |
256 | 0 | } |
257 | 0 | case '%': |
258 | 0 | res = talloc_strdup_append(res, "%"); |
259 | 0 | break; |
260 | 0 | default: |
261 | 0 | goto error_exit; |
262 | 0 | } |
263 | 0 | } |
264 | | |
265 | 0 | res = talloc_strdup_append(res, template); |
266 | 0 | res = talloc_asprintf_append(res, ".%s", file_ext); |
267 | 0 | char *fname = mp_get_user_path(NULL, mpctx->global, res); |
268 | 0 | talloc_free(res); |
269 | 0 | return fname; |
270 | | |
271 | 0 | error_exit: |
272 | 0 | talloc_free(res); |
273 | 0 | return NULL; |
274 | 0 | } |
275 | | |
276 | | static char *gen_fname(struct mp_cmd_ctx *cmd, const char *file_ext) |
277 | 0 | { |
278 | 0 | struct MPContext *mpctx = cmd->mpctx; |
279 | 0 | screenshot_ctx *ctx = mpctx->screenshot_ctx; |
280 | |
|
281 | 0 | int sequence = 0; |
282 | 0 | for (;;) { |
283 | 0 | int prev_sequence = sequence; |
284 | 0 | char *fname = create_fname(ctx->mpctx, |
285 | 0 | ctx->mpctx->opts->screenshot_template, |
286 | 0 | file_ext, |
287 | 0 | &sequence, |
288 | 0 | &ctx->frameno); |
289 | |
|
290 | 0 | if (!fname) { |
291 | 0 | mp_cmd_msg(cmd, MSGL_ERR, "Invalid screenshot filename " |
292 | 0 | "template! Fix or remove the --screenshot-template " |
293 | 0 | "option."); |
294 | 0 | return NULL; |
295 | 0 | } |
296 | | |
297 | 0 | char *dir = ctx->mpctx->opts->screenshot_dir; |
298 | 0 | if (dir && dir[0]) { |
299 | 0 | void *t = fname; |
300 | 0 | dir = mp_get_user_path(t, ctx->mpctx->global, dir); |
301 | 0 | fname = mp_path_join(NULL, dir, fname); |
302 | |
|
303 | 0 | mp_mkdirp(dir); |
304 | |
|
305 | 0 | talloc_free(t); |
306 | 0 | } |
307 | |
|
308 | 0 | char *full_dir = bstrto0(fname, mp_dirname(fname)); |
309 | 0 | mp_mkdirp(full_dir); |
310 | |
|
311 | 0 | if (!mp_path_exists(fname)) |
312 | 0 | return fname; |
313 | | |
314 | 0 | if (sequence == prev_sequence) { |
315 | 0 | mp_cmd_msg(cmd, MSGL_ERR, "Can't save screenshot, file '%s' " |
316 | 0 | "already exists!", fname); |
317 | 0 | talloc_free(fname); |
318 | 0 | return NULL; |
319 | 0 | } |
320 | | |
321 | 0 | talloc_free(fname); |
322 | 0 | } |
323 | 0 | } |
324 | | |
325 | | static void add_osd(struct MPContext *mpctx, struct mp_image *image, int mode) |
326 | 0 | { |
327 | 0 | bool window = mode == MODE_FULL_WINDOW; |
328 | 0 | struct mp_osd_res res = window ? osd_get_vo_res(mpctx->video_out->osd) : |
329 | 0 | osd_res_from_image_params(&image->params); |
330 | 0 | if (mode == MODE_SUBTITLES || window) { |
331 | 0 | osd_draw_on_image(mpctx->osd, res, mpctx->video_pts, |
332 | 0 | OSD_DRAW_SUB_ONLY, image); |
333 | 0 | } |
334 | 0 | if (window) { |
335 | 0 | osd_draw_on_image(mpctx->osd, res, mpctx->video_pts, |
336 | 0 | OSD_DRAW_OSD_ONLY, image); |
337 | 0 | } |
338 | 0 | } |
339 | | |
340 | | static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode, |
341 | | bool high_depth) |
342 | 0 | { |
343 | 0 | struct mp_image *image = NULL; |
344 | 0 | const struct image_writer_opts *imgopts = mpctx->opts->screenshot_image_opts; |
345 | 0 | if (mode == MODE_SUBTITLES && osd_get_render_subs_in_filter(mpctx->osd)) |
346 | 0 | mode = 0; |
347 | |
|
348 | 0 | if (!mpctx->video_out || !mpctx->video_out->config_ok) |
349 | 0 | return NULL; |
350 | | |
351 | 0 | vo_wait_frame(mpctx->video_out); // important for each-frame mode |
352 | |
|
353 | 0 | bool use_sw = mpctx->opts->screenshot_sw; |
354 | 0 | bool window = mode == MODE_FULL_WINDOW; |
355 | 0 | struct voctrl_screenshot ctrl = { |
356 | 0 | .scaled = window, |
357 | 0 | .subs = mode != 0, |
358 | 0 | .osd = window, |
359 | 0 | .high_bit_depth = high_depth && imgopts->high_bit_depth, |
360 | 0 | .native_csp = image_writer_flexible_csp(imgopts), |
361 | 0 | }; |
362 | 0 | if (!use_sw) |
363 | 0 | vo_control(mpctx->video_out, VOCTRL_SCREENSHOT, &ctrl); |
364 | 0 | image = ctrl.res; |
365 | |
|
366 | 0 | if (!use_sw && !image && window) |
367 | 0 | vo_control(mpctx->video_out, VOCTRL_SCREENSHOT_WIN, &image); |
368 | |
|
369 | 0 | if (!image) { |
370 | 0 | use_sw = true; |
371 | 0 | MP_VERBOSE(mpctx->screenshot_ctx, "Falling back to software screenshot.\n"); |
372 | 0 | image = vo_get_current_frame(mpctx->video_out); |
373 | 0 | } |
374 | | |
375 | | // vo_get_current_frame() can return a hardware frame, which we have to download first. |
376 | 0 | if (image && image->fmt.flags & MP_IMGFLAG_HWACCEL) { |
377 | 0 | struct mp_image *nimage = mp_image_hw_download(image, NULL); |
378 | 0 | talloc_free(image); |
379 | 0 | if (!nimage) |
380 | 0 | return NULL; |
381 | 0 | image = nimage; |
382 | 0 | } |
383 | | |
384 | 0 | if (use_sw && image && window) { |
385 | 0 | if (mp_image_crop_valid(&image->params) && |
386 | 0 | (mp_rect_w(image->params.crop) != image->w || |
387 | 0 | mp_rect_h(image->params.crop) != image->h)) |
388 | 0 | { |
389 | 0 | struct mp_image *nimage = mp_image_new_ref(image); |
390 | 0 | if (!nimage) { |
391 | 0 | MP_ERR(mpctx->screenshot_ctx, "mp_image_new_ref failed!\n"); |
392 | 0 | return NULL; |
393 | 0 | } |
394 | 0 | mp_image_crop_rc(nimage, image->params.crop); |
395 | 0 | talloc_free(image); |
396 | 0 | image = nimage; |
397 | 0 | } |
398 | 0 | struct mp_osd_res res = osd_get_vo_res(mpctx->video_out->osd); |
399 | 0 | struct mp_osd_res image_res = osd_res_from_image_params(&image->params); |
400 | 0 | if (!osd_res_equals(res, image_res)) { |
401 | 0 | struct mp_image *nimage = mp_image_alloc(image->imgfmt, res.w, res.h); |
402 | 0 | if (!nimage) { |
403 | 0 | talloc_free(image); |
404 | 0 | return NULL; |
405 | 0 | } |
406 | 0 | struct mp_sws_context *sws = mp_sws_alloc(NULL); |
407 | 0 | mp_sws_scale(sws, nimage, image); |
408 | 0 | talloc_free(image); |
409 | 0 | talloc_free(sws); |
410 | 0 | image = nimage; |
411 | 0 | } |
412 | 0 | } |
413 | | |
414 | 0 | if (!image) |
415 | 0 | return NULL; |
416 | | |
417 | 0 | if (use_sw && mode != 0) |
418 | 0 | add_osd(mpctx, image, mode); |
419 | 0 | mp_image_params_guess_csp(&image->params); |
420 | 0 | return image; |
421 | 0 | } |
422 | | |
423 | | struct mp_image *convert_image(struct mp_image *image, int destfmt, |
424 | | struct mpv_global *global, struct mp_log *log) |
425 | 0 | { |
426 | 0 | int d_w, d_h; |
427 | 0 | mp_image_params_get_dsize(&image->params, &d_w, &d_h); |
428 | |
|
429 | 0 | struct mp_image_params p = { |
430 | 0 | .imgfmt = destfmt, |
431 | 0 | .w = d_w, |
432 | 0 | .h = d_h, |
433 | 0 | .p_w = 1, |
434 | 0 | .p_h = 1, |
435 | 0 | }; |
436 | 0 | mp_image_params_guess_csp(&p); |
437 | |
|
438 | 0 | if (mp_image_params_equal(&p, &image->params)) |
439 | 0 | return mp_image_new_ref(image); |
440 | | |
441 | 0 | struct mp_image *dst = mp_image_alloc(p.imgfmt, p.w, p.h); |
442 | 0 | if (!dst) { |
443 | 0 | mp_err(log, "Out of memory.\n"); |
444 | 0 | return NULL; |
445 | 0 | } |
446 | 0 | mp_image_copy_attributes(dst, image); |
447 | |
|
448 | 0 | dst->params = p; |
449 | |
|
450 | 0 | struct mp_sws_context *sws = mp_sws_alloc(NULL); |
451 | 0 | sws->log = log; |
452 | 0 | if (global) |
453 | 0 | mp_sws_enable_cmdline_opts(sws, global); |
454 | 0 | bool ok = mp_sws_scale(sws, dst, image) >= 0; |
455 | 0 | talloc_free(sws); |
456 | |
|
457 | 0 | if (!ok) { |
458 | 0 | mp_err(log, "Error when converting image.\n"); |
459 | 0 | talloc_free(dst); |
460 | 0 | return NULL; |
461 | 0 | } |
462 | | |
463 | 0 | return dst; |
464 | 0 | } |
465 | | |
466 | | // mode is the same as in screenshot_get() |
467 | | static struct mp_image *screenshot_get_rgb(struct MPContext *mpctx, int mode, |
468 | | bool high_depth, enum mp_imgfmt format) |
469 | 0 | { |
470 | 0 | struct mp_image *mpi = screenshot_get(mpctx, mode, high_depth); |
471 | 0 | if (!mpi) |
472 | 0 | return NULL; |
473 | 0 | struct mp_image *res = convert_image(mpi, format, mpctx->global, |
474 | 0 | mpctx->log); |
475 | 0 | talloc_free(mpi); |
476 | 0 | return res; |
477 | 0 | } |
478 | | |
479 | | void cmd_screenshot_to_file(void *p) |
480 | 0 | { |
481 | 0 | struct mp_cmd_ctx *cmd = p; |
482 | 0 | struct MPContext *mpctx = cmd->mpctx; |
483 | 0 | const char *filename = cmd->args[0].v.s; |
484 | 0 | int mode = cmd->args[1].v.i; |
485 | 0 | struct image_writer_opts opts = *mpctx->opts->screenshot_image_opts; |
486 | |
|
487 | 0 | char *ext = mp_splitext(filename, NULL); |
488 | 0 | int format = image_writer_format_from_ext(ext); |
489 | 0 | if (format) |
490 | 0 | opts.format = format; |
491 | 0 | bool high_depth = image_writer_high_depth(&opts); |
492 | 0 | struct mp_image *image = screenshot_get(mpctx, mode, high_depth); |
493 | 0 | if (!image) { |
494 | 0 | mp_cmd_msg(cmd, MSGL_ERR, "Taking screenshot failed."); |
495 | 0 | cmd->success = false; |
496 | 0 | return; |
497 | 0 | } |
498 | 0 | char *path = mp_get_user_path(NULL, mpctx->global, filename); |
499 | 0 | cmd->success = write_screenshot(cmd, image, path, &opts, true); |
500 | 0 | talloc_free(image); |
501 | 0 | talloc_free(path); |
502 | 0 | } |
503 | | |
504 | | void cmd_screenshot(void *p) |
505 | 0 | { |
506 | 0 | struct mp_cmd_ctx *cmd = p; |
507 | 0 | struct MPContext *mpctx = cmd->mpctx; |
508 | 0 | struct mpv_node *res = &cmd->result; |
509 | 0 | int mode = cmd->args[0].v.i & 3; |
510 | 0 | bool each_frame_toggle = (cmd->args[0].v.i | cmd->args[1].v.i) & 8; |
511 | 0 | bool each_frame_mode = cmd->args[0].v.i & 16; |
512 | |
|
513 | 0 | screenshot_ctx *ctx = mpctx->screenshot_ctx; |
514 | |
|
515 | 0 | if (mode == MODE_SUBTITLES && osd_get_render_subs_in_filter(mpctx->osd)) |
516 | 0 | mode = 0; |
517 | |
|
518 | 0 | if (!each_frame_mode) { |
519 | 0 | if (each_frame_toggle) { |
520 | 0 | if (ctx->each_frame) { |
521 | 0 | TA_FREEP(&ctx->each_frame); |
522 | 0 | return; |
523 | 0 | } |
524 | 0 | ctx->each_frame = talloc_steal(ctx, mp_cmd_clone(cmd->cmd)); |
525 | 0 | ctx->each_frame->args[0].v.i |= 16; |
526 | 0 | } else { |
527 | 0 | TA_FREEP(&ctx->each_frame); |
528 | 0 | } |
529 | 0 | } |
530 | | |
531 | 0 | cmd->success = false; |
532 | |
|
533 | 0 | struct image_writer_opts *opts = mpctx->opts->screenshot_image_opts; |
534 | 0 | bool high_depth = image_writer_high_depth(opts); |
535 | |
|
536 | 0 | struct mp_image *image = screenshot_get(mpctx, mode, high_depth); |
537 | |
|
538 | 0 | if (image) { |
539 | 0 | char *filename = gen_fname(cmd, image_writer_file_ext(opts)); |
540 | 0 | if (filename) { |
541 | 0 | cmd->success = write_screenshot(cmd, image, filename, NULL, false); |
542 | 0 | if (cmd->success) { |
543 | 0 | node_init(res, MPV_FORMAT_NODE_MAP, NULL); |
544 | 0 | node_map_add_string(res, "filename", filename); |
545 | 0 | } |
546 | 0 | } |
547 | 0 | talloc_free(filename); |
548 | 0 | } else { |
549 | 0 | mp_cmd_msg(cmd, MSGL_ERR, "Taking screenshot failed."); |
550 | 0 | } |
551 | |
|
552 | 0 | talloc_free(image); |
553 | 0 | } |
554 | | |
555 | | void cmd_screenshot_raw(void *p) |
556 | 0 | { |
557 | 0 | struct mp_cmd_ctx *cmd = p; |
558 | 0 | struct MPContext *mpctx = cmd->mpctx; |
559 | 0 | struct mpv_node *res = &cmd->result; |
560 | |
|
561 | 0 | const enum mp_imgfmt formats[] = {IMGFMT_BGR0, IMGFMT_BGRA, IMGFMT_RGBA, IMGFMT_RGBA64}; |
562 | 0 | const char *format_names[] = {"bgr0", "bgra", "rgba", "rgba64"}; |
563 | 0 | int idx = cmd->args[1].v.i; |
564 | 0 | mp_assert(idx >= 0 && idx <= 3); |
565 | | |
566 | 0 | bool high_depth = formats[idx] == IMGFMT_RGBA64; |
567 | 0 | struct mp_image *img = screenshot_get_rgb(mpctx, cmd->args[0].v.i, |
568 | 0 | high_depth, formats[idx]); |
569 | 0 | if (!img) { |
570 | 0 | cmd->success = false; |
571 | 0 | return; |
572 | 0 | } |
573 | | |
574 | 0 | node_init(res, MPV_FORMAT_NODE_MAP, NULL); |
575 | 0 | node_map_add_int64(res, "w", img->w); |
576 | 0 | node_map_add_int64(res, "h", img->h); |
577 | 0 | node_map_add_int64(res, "stride", img->stride[0]); |
578 | 0 | node_map_add_string(res, "format", format_names[idx]); |
579 | 0 | struct mpv_byte_array *ba = |
580 | 0 | node_map_add(res, "data", MPV_FORMAT_BYTE_ARRAY)->u.ba; |
581 | 0 | *ba = (struct mpv_byte_array){ |
582 | 0 | .data = img->planes[0], |
583 | 0 | .size = img->stride[0] * img->h, |
584 | 0 | }; |
585 | 0 | talloc_steal(ba, img); |
586 | 0 | } |
587 | | |
588 | | static void screenshot_fin(struct mp_cmd_ctx *cmd) |
589 | 0 | { |
590 | 0 | void **a = cmd->on_completion_priv; |
591 | 0 | struct MPContext *mpctx = a[0]; |
592 | 0 | struct mp_waiter *waiter = a[1]; |
593 | |
|
594 | 0 | mp_waiter_wakeup(waiter, 0); |
595 | 0 | mp_wakeup_core(mpctx); |
596 | 0 | } |
597 | | |
598 | | void handle_each_frame_screenshot(struct MPContext *mpctx) |
599 | 7.89M | { |
600 | 7.89M | screenshot_ctx *ctx = mpctx->screenshot_ctx; |
601 | | |
602 | 7.89M | if (!ctx->each_frame) |
603 | 7.89M | return; |
604 | | |
605 | 0 | if (ctx->last_frame_count == mpctx->shown_vframes) |
606 | 0 | return; |
607 | 0 | ctx->last_frame_count = mpctx->shown_vframes; |
608 | |
|
609 | 0 | struct mp_waiter wait = MP_WAITER_INITIALIZER; |
610 | 0 | void *a[] = {mpctx, &wait}; |
611 | 0 | run_command(mpctx, mp_cmd_clone(ctx->each_frame), NULL, screenshot_fin, a); |
612 | | |
613 | | // Block (in a reentrant way) until the screenshot was written. Otherwise, |
614 | | // we could pile up screenshot requests forever. |
615 | 0 | while (!mp_waiter_poll(&wait)) |
616 | 0 | mp_idle(mpctx); |
617 | |
|
618 | 0 | mp_waiter_wait(&wait); |
619 | 0 | } |