summaryrefslogtreecommitdiffstats
path: root/core/screenshot.c
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2012-11-05 17:02:04 +0100
committerwm4 <wm4@nowhere>2012-11-12 20:06:14 +0100
commitd4bdd0473d6f43132257c9fb3848d829755167a3 (patch)
tree8021c2f7da1841393c8c832105e20cd527826d6c /core/screenshot.c
parentbd48deba77bd5582c5829d6fe73a7d2571088aba (diff)
downloadmpv-d4bdd0473d6f43132257c9fb3848d829755167a3.tar.bz2
mpv-d4bdd0473d6f43132257c9fb3848d829755167a3.tar.xz
Rename directories, move files (step 1 of 2) (does not compile)
Tis drops the silly lib prefixes, and attempts to organize the tree in a more logical way. Make the top-level directory less cluttered as well. Renames the following directories: libaf -> audio/filter libao2 -> audio/out libvo -> video/out libmpdemux -> demux Split libmpcodecs: vf* -> video/filter vd*, dec_video.* -> video/decode mp_image*, img_format*, ... -> video/ ad*, dec_audio.* -> audio/decode libaf/format.* is moved to audio/ - this is similar to how mp_image.* is located in video/. Move most top-level .c/.h files to core. (talloc.c/.h is left on top- level, because it's external.) Park some of the more annoying files in compat/. Some of these are relicts from the time mplayer used ffmpeg internals. sub/ is not split, because it's too much of a mess (subtitle code is mixed with OSD display and rendering). Maybe the organization of core is not ideal: it mixes playback core (like mplayer.c) and utility helpers (like bstr.c/h). Should the need arise, the playback core will be moved somewhere else, while core contains all helper and common code.
Diffstat (limited to 'core/screenshot.c')
-rw-r--r--core/screenshot.c369
1 files changed, 369 insertions, 0 deletions
diff --git a/core/screenshot.c b/core/screenshot.c
new file mode 100644
index 0000000000..b84bb6d340
--- /dev/null
+++ b/core/screenshot.c
@@ -0,0 +1,369 @@
+/*
+ * 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 "bstr.h"
+#include "mp_msg.h"
+#include "path.h"
+#include "libmpcodecs/mp_image.h"
+#include "libmpcodecs/dec_video.h"
+#include "libmpcodecs/vf.h"
+#include "libvo/video_out.h"
+#include "image_writer.h"
+#include "sub/sub.h"
+
+#include "libvo/csputils.h"
+
+#define MODE_FULL_WINDOW 1
+#define MODE_SUBTITLES 2
+
+typedef struct screenshot_ctx {
+ struct MPContext *mpctx;
+
+ int mode;
+ int each_frame;
+ int using_vf_screenshot;
+
+ 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,
+ };
+}
+
+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 't': {
+ char fmt = *template;
+ if (!fmt)
+ goto error_exit;
+ template++;
+ char fmtstr[] = {'%', fmt, '\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) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "Invalid screenshot filename "
+ "template! Fix or remove the --screenshot-template option."
+ "\n");
+ return NULL;
+ }
+
+ if (!mp_path_exists(fname))
+ return fname;
+
+ if (sequence == prev_sequence) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "Can't save screenshot, file '%s' "
+ "already exists!\n", fname);
+ talloc_free(fname);
+ return NULL;
+ }
+
+ talloc_free(fname);
+ }
+}
+
+static struct mp_image *add_subs(struct MPContext *mpctx,
+ struct mp_image *image)
+{
+ if (!(image->flags & MP_IMGFLAG_ALLOCATED)) {
+ struct mp_image *new_image = alloc_mpi(image->width, image->height,
+ image->imgfmt);
+ copy_mpi(new_image, image);
+ vf_clone_mpi_attributes(new_image, image);
+ image = new_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->width / image->height;
+ 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);
+
+ return image;
+}
+
+static void screenshot_save(struct MPContext *mpctx, struct mp_image *image,
+ bool with_subs)
+{
+ screenshot_ctx *ctx = mpctx->screenshot_ctx;
+
+ struct image_writer_opts *opts = mpctx->opts.screenshot_image_opts;
+
+ struct mp_image *new_image = image;
+ if (with_subs)
+ new_image = add_subs(mpctx, new_image);
+
+ char *filename = gen_fname(ctx, image_writer_file_ext(opts));
+ if (filename) {
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "*** screenshot '%s' ***\n", filename);
+ if (!write_image(new_image, opts, filename))
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "\nError writing screenshot!\n");
+ talloc_free(filename);
+ }
+
+ if (new_image != image)
+ free_mp_image(new_image);
+}
+
+static void vf_screenshot_callback(void *pctx, struct mp_image *image)
+{
+ struct MPContext *mpctx = (struct MPContext *)pctx;
+ screenshot_ctx *ctx = mpctx->screenshot_ctx;
+ screenshot_save(mpctx, image, ctx->mode);
+ if (ctx->each_frame)
+ screenshot_request(mpctx, ctx->mode, false);
+}
+
+static bool force_vf(struct MPContext *mpctx)
+{
+ if (mpctx->sh_video) {
+ struct vf_instance *vf = mpctx->sh_video->vfilter;
+ while (vf) {
+ if (strcmp(vf->info->name, "screenshot_force") == 0)
+ return true;
+ vf = vf->next;
+ }
+ }
+ return false;
+}
+
+void screenshot_request(struct MPContext *mpctx, int mode, bool each_frame)
+{
+ if (mpctx->video_out && mpctx->video_out->config_ok) {
+ screenshot_ctx *ctx = mpctx->screenshot_ctx;
+
+ ctx->using_vf_screenshot = 0;
+
+ if (mode == MODE_SUBTITLES && mpctx->osd->render_subs_in_filter)
+ mode = 0;
+
+ if (each_frame) {
+ ctx->each_frame = !ctx->each_frame;
+ ctx->mode = mode;
+ if (!ctx->each_frame)
+ return;
+ }
+
+ struct voctrl_screenshot_args args =
+ { .full_window = (mode == MODE_FULL_WINDOW) };
+ if (!force_vf(mpctx)
+ && vo_control(mpctx->video_out, VOCTRL_SCREENSHOT, &args) == true)
+ {
+ if (args.has_osd)
+ mode = 0;
+ screenshot_save(mpctx, args.out_image, mode == MODE_SUBTITLES);
+ free_mp_image(args.out_image);
+ } else {
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "No VO support for taking"
+ " screenshots, trying VFCTRL_SCREENSHOT!\n");
+ ctx->using_vf_screenshot = 1;
+ struct vf_ctrl_screenshot cmd = {
+ .image_callback = vf_screenshot_callback,
+ .image_callback_ctx = mpctx,
+ };
+ struct vf_instance *vfilter = mpctx->sh_video->vfilter;
+ if (vfilter->control(vfilter, VFCTRL_SCREENSHOT, &cmd) !=
+ CONTROL_OK)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO,
+ "...failed (need --vf=screenshot?)\n");
+ }
+ }
+}
+
+void screenshot_flip(struct MPContext *mpctx)
+{
+ screenshot_ctx *ctx = mpctx->screenshot_ctx;
+
+ if (!ctx->each_frame)
+ return;
+
+ // screenshot_flip is called when the VO presents a new frame. vf_screenshot
+ // can behave completely different (consider filters inserted between
+ // vf_screenshot and vf_vo, that add or remove frames), so handle this case
+ // somewhere else.
+ if (ctx->using_vf_screenshot)
+ return;
+
+ screenshot_request(mpctx, ctx->mode, false);
+}