summaryrefslogtreecommitdiffstats
path: root/mpvcore/player/screenshot.c
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2013-10-29 21:35:29 +0100
committerwm4 <wm4@nowhere>2013-10-30 01:51:28 +0100
commita84258d769a3d06958a017bb3fc47521ade5751b (patch)
treed502084fdaca4125059364658575fd0795d32c84 /mpvcore/player/screenshot.c
parent884b4600a478a1a4b56c53e71dd697ca6773494b (diff)
downloadmpv-a84258d769a3d06958a017bb3fc47521ade5751b.tar.bz2
mpv-a84258d769a3d06958a017bb3fc47521ade5751b.tar.xz
Move files part of the playback core to player sub-directory
All these files access mp_core.h and MPContext, and form the actual player application. They should be all in one place, and separate from the other sources that are mere utility helpers. Preparation for splitting mplayer.c into multiple smaller parts.
Diffstat (limited to 'mpvcore/player/screenshot.c')
-rw-r--r--mpvcore/player/screenshot.c405
1 files changed, 405 insertions, 0 deletions
diff --git a/mpvcore/player/screenshot.c b/mpvcore/player/screenshot.c
new file mode 100644
index 0000000000..bafb3012fb
--- /dev/null
+++ b/mpvcore/player/screenshot.c
@@ -0,0 +1,405 @@
+/*
+ * 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 "mp_osd.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/sub.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_tmsg(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);
+ return talloc_asprintf_append(res, ".%s", file_ext);
+
+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,
+ .video_par = dar / sar,
+ };
+
+ 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->sh_video && mpctx->sh_video->vfilter) {
+ struct vf_instance *vfilter = mpctx->sh_video->vfilter;
+ vfilter->control(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);
+}