mirror of
https://github.com/mpv-player/mpv
synced 2024-12-28 10:02:17 +00:00
0a0bb9059f
MPlayer traditionally always used the display aspect ratio, e.g. 16:9, while FFmpeg uses the sample (aka pixel) aspect ratio. Both have a bunch of advantages and disadvantages. Actually, it seems using sample aspect ratio is generally nicer. The main reason for the change is making mpv closer to how FFmpeg works in order to make life easier. It's also nice that everything uses integer fractions instead of floats now (except --video-aspect option/property). Note that there is at least 1 user-visible change: vf_dsize now does not set the display size, only the display aspect ratio. This is because the image_params d_w/d_h fields did not just set the display aspect, but also the size (except in encoding mode).
440 lines
12 KiB
C
440 lines
12 KiB
C
/*
|
|
* This file is part of mpv.
|
|
*
|
|
* mpv 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.
|
|
*
|
|
* mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include "config.h"
|
|
|
|
#include "osdep/io.h"
|
|
|
|
#include "talloc.h"
|
|
#include "screenshot.h"
|
|
#include "core.h"
|
|
#include "command.h"
|
|
#include "misc/bstr.h"
|
|
#include "common/msg.h"
|
|
#include "options/path.h"
|
|
#include "video/mp_image.h"
|
|
#include "video/decode/dec_video.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(ctx->mpctx, status == SMSG_ERR ? MSGL_ERR : MSGL_INFO, "%s\n", s);
|
|
if (ctx->osd)
|
|
set_osd_msg(ctx->mpctx, 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')
|
|
return NULL;
|
|
|
|
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 = NULL;
|
|
if (mpctx->filename)
|
|
video_file = mp_basename(mpctx->filename);
|
|
|
|
if (!video_file)
|
|
video_file = "NO_FILE";
|
|
|
|
char *name = video_file;
|
|
if (fmt == 'F')
|
|
name = stripext(res, video_file);
|
|
append_filename(&res, name);
|
|
break;
|
|
}
|
|
case 'x':
|
|
case 'X': {
|
|
char *fallback = "";
|
|
if (fmt == 'X') {
|
|
if (template[0] != '{')
|
|
goto error_exit;
|
|
char *end = strchr(template, '}');
|
|
if (!end)
|
|
goto error_exit;
|
|
fallback = talloc_strndup(res, template + 1, end - template - 1);
|
|
template = end + 1;
|
|
}
|
|
if (!mpctx->filename || mp_is_url(bstr0(mpctx->filename))) {
|
|
res = talloc_strdup_append(res, fallback);
|
|
} else {
|
|
bstr dir = mp_dirname(mpctx->filename);
|
|
if (!bstr_equals0(dir, "."))
|
|
res = talloc_asprintf_append(res, "%.*s", BSTR_P(dir));
|
|
}
|
|
break;
|
|
}
|
|
case 'p':
|
|
case 'P': {
|
|
char *t = mp_format_time(get_playback_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_playback_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, mpctx->global, 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;
|
|
}
|
|
|
|
char *dir = ctx->mpctx->opts->screenshot_directory;
|
|
if (dir && dir[0]) {
|
|
void *t = fname;
|
|
dir = mp_get_user_path(t, ctx->mpctx->global, dir);
|
|
fname = mp_path_join(NULL, dir, fname);
|
|
talloc_free(t);
|
|
|
|
mp_mkdirp(dir);
|
|
}
|
|
|
|
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)
|
|
{
|
|
struct mp_osd_res res = osd_res_from_image_params(&image->params);
|
|
osd_draw_on_image(mpctx->osd, res, mpctx->video_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, mpctx->log))
|
|
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 (mode == MODE_SUBTITLES && osd_get_render_subs_in_filter(mpctx->osd))
|
|
mode = 0;
|
|
|
|
if (mpctx->video_out && mpctx->video_out->config_ok) {
|
|
vo_wait_frame(mpctx->video_out); // important for each-frame mode
|
|
|
|
if (mode != MODE_FULL_WINDOW)
|
|
image = vo_get_current_frame(mpctx->video_out);
|
|
if (!image) {
|
|
vo_control(mpctx->video_out, VOCTRL_SCREENSHOT_WIN, &image);
|
|
mode = MODE_FULL_WINDOW;
|
|
}
|
|
}
|
|
|
|
if (image && mpctx->d_video && mpctx->d_video->hwdec_info) {
|
|
struct mp_hwdec_ctx *ctx = mpctx->d_video->hwdec_info->hwctx;
|
|
struct mp_image *nimage = NULL;
|
|
if (ctx && ctx->download_image && (image->fmt.flags & MP_IMGFLAG_HWACCEL))
|
|
nimage = ctx->download_image(ctx, image, NULL);
|
|
if (nimage) {
|
|
talloc_free(image);
|
|
image = nimage;
|
|
}
|
|
}
|
|
|
|
if (image && mode == MODE_SUBTITLES)
|
|
add_subs(mpctx, image);
|
|
|
|
return image;
|
|
}
|
|
|
|
struct mp_image *screenshot_get_rgb(struct MPContext *mpctx, int mode)
|
|
{
|
|
struct mp_image *mpi = screenshot_get(mpctx, mode);
|
|
if (!mpi)
|
|
return NULL;
|
|
struct mp_image *res = convert_image(mpi, IMGFMT_BGR0, mpctx->log);
|
|
talloc_free(mpi);
|
|
return res;
|
|
}
|
|
|
|
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;
|
|
|
|
char *ext = mp_splitext(filename, NULL);
|
|
if (ext)
|
|
opts.format = ext;
|
|
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, mpctx->log))
|
|
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 && osd_get_render_subs_in_filter(mpctx->osd))
|
|
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);
|
|
}
|