summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCS/man/en/input.rst13
-rw-r--r--core/command.c4
-rw-r--r--core/input/input.c6
-rw-r--r--core/input/input.h1
-rw-r--r--core/path.c11
-rw-r--r--core/path.h7
-rw-r--r--core/screenshot.c96
-rw-r--r--core/screenshot.h6
8 files changed, 116 insertions, 28 deletions
diff --git a/DOCS/man/en/input.rst b/DOCS/man/en/input.rst
index c9adc7a4ca..f22536ed99 100644
--- a/DOCS/man/en/input.rst
+++ b/DOCS/man/en/input.rst
@@ -140,6 +140,19 @@ List of Input Commands
Take a screenshot each frame. Issue this command again to stop taking
screenshots.
+``screenshot_to_file "<filename>" [subtitles|video|window]``
+ Take a screenshot and save it to a given file. The format of the file will
+ be guessed by the extension (and ``--screenshot-format`` is ignored - the
+ behavior when the extension is missing or unknown is arbitrary).
+
+ The second argument is like the first argument to ``screenshot``.
+
+ This command tries to never overwrite files. If the file already exists,
+ it fails.
+
+ Like all input command parameters, the filename is subject to property
+ expansion as described in `Property Expansion`_.
+
``playlist_next [weak|force]``
Go to the next entry on the playlist.
diff --git a/core/command.c b/core/command.c
index 848ebb848f..6056596d04 100644
--- a/core/command.c
+++ b/core/command.c
@@ -2420,6 +2420,10 @@ void run_command(MPContext *mpctx, mp_cmd_t *cmd)
screenshot_request(mpctx, cmd->args[0].v.i, cmd->args[1].v.i, msg_osd);
break;
+ case MP_CMD_SCREENSHOT_TO_FILE:
+ screenshot_to_file(mpctx, cmd->args[0].v.s, cmd->args[1].v.i, msg_osd);
+ break;
+
case MP_CMD_RUN:
#ifndef __MINGW32__
if (!fork()) {
diff --git a/core/input/input.c b/core/input/input.c
index ce7b868c02..e87d9bb560 100644
--- a/core/input/input.c
+++ b/core/input/input.c
@@ -171,6 +171,12 @@ static const mp_cmd_t mp_cmds[] = {
OARG_CHOICE(0, ({"single", 0},
{"each-frame", 1})),
}},
+ { MP_CMD_SCREENSHOT_TO_FILE, "screenshot_to_file", {
+ ARG_STRING,
+ OARG_CHOICE(2, ({"video", 0},
+ {"window", 1},
+ {"subtitles", 2})),
+ }},
{ MP_CMD_LOADFILE, "loadfile", {
ARG_STRING,
OARG_CHOICE(0, ({"replace", 0}, {"0", 0},
diff --git a/core/input/input.h b/core/input/input.h
index f7d69a0de7..da92efd373 100644
--- a/core/input/input.h
+++ b/core/input/input.h
@@ -36,6 +36,7 @@ enum mp_command_type {
MP_CMD_TV_STEP_NORM,
MP_CMD_TV_STEP_CHANNEL_LIST,
MP_CMD_SCREENSHOT,
+ MP_CMD_SCREENSHOT_TO_FILE,
MP_CMD_LOADFILE,
MP_CMD_LOADLIST,
MP_CMD_PLAYLIST_CLEAR,
diff --git a/core/path.c b/core/path.c
index 1cff719041..50350be18c 100644
--- a/core/path.c
+++ b/core/path.c
@@ -164,6 +164,17 @@ struct bstr mp_dirname(const char *path)
return ret;
}
+char *mp_splitext(const char *path, bstr *root)
+{
+ assert(path);
+ const char *split = strrchr(path, '.');
+ if (!split)
+ split = path + strlen(path);
+ if (root)
+ *root = (bstr){.start = (char *)path, .len = path - split};
+ return (char *)split;
+}
+
char *mp_path_join(void *talloc_ctx, struct bstr p1, struct bstr p2)
{
if (p1.len == 0)
diff --git a/core/path.h b/core/path.h
index a3033199df..a38ad503ea 100644
--- a/core/path.h
+++ b/core/path.h
@@ -40,6 +40,13 @@ char *mp_find_user_config_file(const char *filename);
char *mp_basename(const char *path);
+/* Return file extension, including the '.'. If root is not NULL, set it to the
+ * part of the path without extension. So: path == root + returnvalue
+ * Don't consider it a file extension if the only '.' is the first character.
+ * Return "" if no extension.
+ */
+char *mp_splitext(const char *path, bstr *root);
+
/* Return struct bstr referencing directory part of path, or if that
* would be empty, ".".
*/
diff --git a/core/screenshot.c b/core/screenshot.c
index 4f7a0dbcdd..6b07bce5a0 100644
--- a/core/screenshot.c
+++ b/core/screenshot.c
@@ -278,16 +278,12 @@ static void add_subs(struct MPContext *mpctx, struct mp_image *image)
OSD_DRAW_SUB_ONLY, image);
}
-static void screenshot_save(struct MPContext *mpctx, struct mp_image *image,
- bool with_subs)
+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;
- if (with_subs)
- add_subs(mpctx, image);
-
char *filename = gen_fname(ctx, image_writer_file_ext(opts));
if (filename) {
screenshot_msg(ctx, SMSG_OK, "Screenshot: '%s'", filename);
@@ -295,30 +291,15 @@ static void screenshot_save(struct MPContext *mpctx, struct mp_image *image,
screenshot_msg(ctx, SMSG_ERR, "Error writing screenshot!");
talloc_free(filename);
}
-
- talloc_free(image);
}
-void screenshot_request(struct MPContext *mpctx, int mode, bool each_frame,
- bool osd)
+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) {
- 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 voctrl_screenshot_args args =
{ .full_window = (mode == MODE_FULL_WINDOW) };
@@ -328,14 +309,73 @@ void screenshot_request(struct MPContext *mpctx, int mode, bool each_frame,
if (!args.out_image)
vo_control(mpctx->video_out, VOCTRL_SCREENSHOT, &args);
- if (args.out_image) {
- if (args.has_osd)
- mode = 0;
- screenshot_save(mpctx, args.out_image, mode == MODE_SUBTITLES);
- } else {
- screenshot_msg(ctx, SMSG_ERR, "Taking screenshot failed.");
+ 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)
diff --git a/core/screenshot.h b/core/screenshot.h
index 916a875505..1b12ac9b73 100644
--- a/core/screenshot.h
+++ b/core/screenshot.h
@@ -34,6 +34,12 @@ void screenshot_init(struct MPContext *mpctx);
void screenshot_request(struct MPContext *mpctx, int mode, bool each_frame,
bool osd);
+// filename: where to store the screenshot; doesn't try to find an alternate
+// name if the file already exists
+// mode, osd: same as in screenshot_request()
+void screenshot_to_file(struct MPContext *mpctx, const char *filename, int mode,
+ bool osd);
+
// Called by the playback core code when a new frame is displayed.
void screenshot_flip(struct MPContext *mpctx);