summaryrefslogtreecommitdiffstats
path: root/player/screenshot.c
diff options
context:
space:
mode:
Diffstat (limited to 'player/screenshot.c')
-rw-r--r--player/screenshot.c404
1 files changed, 404 insertions, 0 deletions
diff --git a/player/screenshot.c b/player/screenshot.c
new file mode 100644
index 0000000000..7aaba5f05a
--- /dev/null
+++ b/player/screenshot.c
@@ -0,0 +1,404 @@
+/*
+ * This file is part of mplayer2.
+ *
+ * mplayer2 is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * mplayer2 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mplayer2; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "config.h"
+
+#include "osdep/io.h"
+
+#include "talloc.h"
+#include "screenshot.h"
+#include "mp_core.h"
+#include "command.h"
+#include "mpvcore/bstr.h"
+#include "mpvcore/mp_msg.h"
+#include "mpvcore/path.h"
+#include "video/mp_image.h"
+#include "video/decode/dec_video.h"
+#include "video/filter/vf.h"
+#include "video/out/vo.h"
+#include "video/image_writer.h"
+#include "sub/osd.h"
+
+#include "video/csputils.h"
+
+#define MODE_FULL_WINDOW 1
+#define MODE_SUBTITLES 2
+
+typedef struct screenshot_ctx {
+ struct MPContext *mpctx;
+
+ int mode;
+ bool each_frame;
+ bool osd;
+
+ int frameno;
+} screenshot_ctx;
+
+void screenshot_init(struct MPContext *mpctx)
+{
+ mpctx->screenshot_ctx = talloc(mpctx, screenshot_ctx);
+ *mpctx->screenshot_ctx = (screenshot_ctx) {
+ .mpctx = mpctx,
+ .frameno = 1,
+ };
+}
+
+#define SMSG_OK 0
+#define SMSG_ERR 1
+
+static void screenshot_msg(screenshot_ctx *ctx, int status, const char *msg,
+ ...) PRINTF_ATTRIBUTE(3,4);
+
+static void screenshot_msg(screenshot_ctx *ctx, int status, const char *msg,
+ ...)
+{
+ va_list ap;
+ char *s;
+
+ va_start(ap, msg);
+ s = talloc_vasprintf(NULL, msg, ap);
+ va_end(ap);
+
+ mp_msg(MSGT_CPLAYER, status == SMSG_ERR ? MSGL_ERR : MSGL_INFO, "%s\n", s);
+ if (ctx->osd) {
+ set_osd_msg(ctx->mpctx, OSD_MSG_TEXT, 1, ctx->mpctx->opts->osd_duration,
+ "%s", s);
+ }
+
+ talloc_free(s);
+}
+
+static char *stripext(void *talloc_ctx, const char *s)
+{
+ const char *end = strrchr(s, '.');
+ if (!end)
+ end = s + strlen(s);
+ return talloc_asprintf(talloc_ctx, "%.*s", (int)(end - s), s);
+}
+
+#ifdef _WIN32
+#define ILLEGAL_FILENAME_CHARS "?\"/\\<>*|:"
+#else
+#define ILLEGAL_FILENAME_CHARS "/"
+#endif
+
+// Replace all characters disallowed in filenames with '_' and return the newly
+// allocated result string.
+static char *sanitize_filename(void *talloc_ctx, const char *s)
+{
+ char *res = talloc_strdup(talloc_ctx, s);
+ char *cur = res;
+ while (*cur) {
+ if (strchr(ILLEGAL_FILENAME_CHARS, *cur) || ((unsigned char)*cur) < 32)
+ *cur = '_';
+ cur++;
+ }
+ return res;
+}
+
+static void append_filename(char **s, const char *f)
+{
+ char *append = sanitize_filename(NULL, f);
+ *s = talloc_strdup_append(*s, append);
+ talloc_free(append);
+}
+
+static char *create_fname(struct MPContext *mpctx, char *template,
+ const char *file_ext, int *sequence, int *frameno)
+{
+ char *res = talloc_strdup(NULL, ""); //empty string, non-NULL context
+
+ time_t raw_time = time(NULL);
+ struct tm *local_time = localtime(&raw_time);
+
+ if (!template || *template == '\0')
+ template = "shot%n";
+
+ for (;;) {
+ char *next = strchr(template, '%');
+ if (!next)
+ break;
+ res = talloc_strndup_append(res, template, next - template);
+ template = next + 1;
+ char fmt = *template++;
+ switch (fmt) {
+ case '#':
+ case '0':
+ case 'n': {
+ int digits = '4';
+ if (fmt == '#') {
+ if (!*sequence) {
+ *frameno = 1;
+ }
+ fmt = *template++;
+ }
+ if (fmt == '0') {
+ digits = *template++;
+ if (digits < '0' || digits > '9')
+ goto error_exit;
+ fmt = *template++;
+ }
+ if (fmt != 'n')
+ goto error_exit;
+ char fmtstr[] = {'%', '0', digits, 'd', '\0'};
+ res = talloc_asprintf_append(res, fmtstr, *frameno);
+ if (*frameno < 100000 - 1) {
+ (*frameno) += 1;
+ (*sequence) += 1;
+ }
+ break;
+ }
+ case 'f':
+ case 'F': {
+ char *video_file = mp_basename(mpctx->filename);
+ if (video_file) {
+ char *name = video_file;
+ if (fmt == 'F')
+ name = stripext(res, video_file);
+ append_filename(&res, name);
+ }
+ break;
+ }
+ case 'p':
+ case 'P': {
+ char *t = mp_format_time(get_current_time(mpctx), fmt == 'P');
+ append_filename(&res, t);
+ talloc_free(t);
+ break;
+ }
+ case 'w': {
+ char tfmt = *template;
+ if (!tfmt)
+ goto error_exit;
+ template++;
+ char fmtstr[] = {'%', tfmt, '\0'};
+ char *s = mp_format_time_fmt(fmtstr, get_current_time(mpctx));
+ if (!s)
+ goto error_exit;
+ append_filename(&res, s);
+ talloc_free(s);
+ break;
+ }
+ case 't': {
+ char tfmt = *template;
+ if (!tfmt)
+ goto error_exit;
+ template++;
+ char fmtstr[] = {'%', tfmt, '\0'};
+ char buffer[80];
+ if (strftime(buffer, sizeof(buffer), fmtstr, local_time) == 0)
+ buffer[0] = '\0';
+ append_filename(&res, buffer);
+ break;
+ }
+ case '{': {
+ char *end = strchr(template, '}');
+ if (!end)
+ goto error_exit;
+ struct bstr prop = bstr_splice(bstr0(template), 0, end - template);
+ char *tmp = talloc_asprintf(NULL, "${%.*s}", BSTR_P(prop));
+ char *s = mp_property_expand_string(mpctx, tmp);
+ talloc_free(tmp);
+ if (s)
+ append_filename(&res, s);
+ talloc_free(s);
+ template = end + 1;
+ break;
+ }
+ case '%':
+ res = talloc_strdup_append(res, "%");
+ break;
+ default:
+ goto error_exit;
+ }
+ }
+
+ res = talloc_strdup_append(res, template);
+ res = talloc_asprintf_append(res, ".%s", file_ext);
+ char *fname = mp_get_user_path(NULL, res);
+ talloc_free(res);
+ return fname;
+
+error_exit:
+ talloc_free(res);
+ return NULL;
+}
+
+static char *gen_fname(screenshot_ctx *ctx, const char *file_ext)
+{
+ int sequence = 0;
+ for (;;) {
+ int prev_sequence = sequence;
+ char *fname = create_fname(ctx->mpctx,
+ ctx->mpctx->opts->screenshot_template,
+ file_ext,
+ &sequence,
+ &ctx->frameno);
+
+ if (!fname) {
+ screenshot_msg(ctx, SMSG_ERR, "Invalid screenshot filename "
+ "template! Fix or remove the --screenshot-template "
+ "option.");
+ return NULL;
+ }
+
+ if (!mp_path_exists(fname))
+ return fname;
+
+ if (sequence == prev_sequence) {
+ screenshot_msg(ctx, SMSG_ERR, "Can't save screenshot, file '%s' "
+ "already exists!", fname);
+ talloc_free(fname);
+ return NULL;
+ }
+
+ talloc_free(fname);
+ }
+}
+
+static void add_subs(struct MPContext *mpctx, struct mp_image *image)
+{
+ int d_w = image->display_w ? image->display_w : image->w;
+ int d_h = image->display_h ? image->display_h : image->h;
+
+ double sar = (double)image->w / image->h;
+ double dar = (double)d_w / d_h;
+ struct mp_osd_res res = {
+ .w = image->w,
+ .h = image->h,
+ .display_par = sar / dar,
+ };
+
+ osd_draw_on_image(mpctx->osd, res, mpctx->osd->vo_pts,
+ OSD_DRAW_SUB_ONLY, image);
+}
+
+static void screenshot_save(struct MPContext *mpctx, struct mp_image *image)
+{
+ screenshot_ctx *ctx = mpctx->screenshot_ctx;
+
+ struct image_writer_opts *opts = mpctx->opts->screenshot_image_opts;
+
+ char *filename = gen_fname(ctx, image_writer_file_ext(opts));
+ if (filename) {
+ screenshot_msg(ctx, SMSG_OK, "Screenshot: '%s'", filename);
+ if (!write_image(image, opts, filename))
+ screenshot_msg(ctx, SMSG_ERR, "Error writing screenshot!");
+ talloc_free(filename);
+ }
+}
+
+static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode)
+{
+ struct mp_image *image = NULL;
+ if (mpctx->video_out && mpctx->video_out->config_ok) {
+ if (mode == MODE_SUBTITLES && mpctx->osd->render_subs_in_filter)
+ mode = 0;
+
+ struct voctrl_screenshot_args args =
+ { .full_window = (mode == MODE_FULL_WINDOW) };
+
+ if (mpctx->d_video && mpctx->d_video->vfilter)
+ vf_control_any(mpctx->d_video->vfilter, VFCTRL_SCREENSHOT, &args);
+
+ if (!args.out_image)
+ vo_control(mpctx->video_out, VOCTRL_SCREENSHOT, &args);
+
+ image = args.out_image;
+ if (image) {
+ if (mode == MODE_SUBTITLES && !args.has_osd)
+ add_subs(mpctx, image);
+ }
+ }
+ return image;
+}
+
+void screenshot_to_file(struct MPContext *mpctx, const char *filename, int mode,
+ bool osd)
+{
+ screenshot_ctx *ctx = mpctx->screenshot_ctx;
+ struct image_writer_opts opts = *mpctx->opts->screenshot_image_opts;
+ bool old_osd = ctx->osd;
+ ctx->osd = osd;
+
+ if (mp_path_exists(filename)) {
+ screenshot_msg(ctx, SMSG_ERR, "Screenshot: file '%s' already exists.",
+ filename);
+ goto end;
+ }
+ char *ext = mp_splitext(filename, NULL);
+ if (ext)
+ opts.format = ext + 1; // omit '.'
+ struct mp_image *image = screenshot_get(mpctx, mode);
+ if (!image) {
+ screenshot_msg(ctx, SMSG_ERR, "Taking screenshot failed.");
+ goto end;
+ }
+ screenshot_msg(ctx, SMSG_OK, "Screenshot: '%s'", filename);
+ if (!write_image(image, &opts, filename))
+ screenshot_msg(ctx, SMSG_ERR, "Error writing screenshot!");
+ talloc_free(image);
+
+end:
+ ctx->osd = old_osd;
+}
+
+void screenshot_request(struct MPContext *mpctx, int mode, bool each_frame,
+ bool osd)
+{
+ screenshot_ctx *ctx = mpctx->screenshot_ctx;
+
+ if (mode == MODE_SUBTITLES && mpctx->osd->render_subs_in_filter)
+ mode = 0;
+
+ if (each_frame) {
+ ctx->each_frame = !ctx->each_frame;
+ if (!ctx->each_frame)
+ return;
+ } else {
+ ctx->each_frame = false;
+ }
+
+ ctx->mode = mode;
+ ctx->osd = osd;
+
+ struct mp_image *image = screenshot_get(mpctx, mode);
+
+ if (image) {
+ screenshot_save(mpctx, image);
+ } else {
+ screenshot_msg(ctx, SMSG_ERR, "Taking screenshot failed.");
+ }
+
+ talloc_free(image);
+}
+
+void screenshot_flip(struct MPContext *mpctx)
+{
+ screenshot_ctx *ctx = mpctx->screenshot_ctx;
+
+ if (!ctx->each_frame)
+ return;
+
+ ctx->each_frame = false;
+ screenshot_request(mpctx, ctx->mode, true, ctx->osd);
+}