summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@mplayer2.org>2012-02-29 03:46:25 +0100
committerwm4 <wm4@mplayer2.org>2012-02-29 04:14:54 +0100
commit12c44610ad6963bb58c01cc4415cc7befef386e1 (patch)
treef41a1f7975d253d847e6cb5ecf1537f5f5d9a008
parent8b69d8a366df590c2160b36269dae424f93eabb7 (diff)
downloadmpv-12c44610ad6963bb58c01cc4415cc7befef386e1.tar.bz2
mpv-12c44610ad6963bb58c01cc4415cc7befef386e1.tar.xz
screenshot: make screenshot filenames configurable
This adds the --screenshot-template option, which specifies a template for the filename used for a screenshot. The '%' character is parsed as format specifier. These format specifiers insert metadata into the filename. For example, '%f' is replaced with the filename of the currently played file. The following format specifiers are available: %n Insert sequence number (padded with 4 zeros), e.g. "0002". %0Nn Like %n, but pad to N zeros (N = 0 to 9). %n behaves like %04n. %#n Like %n, but reset the sequence counter on every screenshot. (Useful if other parts in the template make the resulting filename already mostly unique.) %#0Nn Use %0Nn and %#n at the same time. %f Insert filename of the currently played video. %F Like %f, but with stripped file extension ("." and rest). %p Insert current playback time, in HH:MM:SS format. %P Like %p, but adds milliseconds: HH:MM:SS.mmmm %tX Insert the current local date/time, using the date format X. X is a single letter and is passed to strftime() as "%X". E.g. "%td" inserts the number of the current day. %{prop} Insert the value of the slave property 'prop'. E.g. %{filename} is the same as %f. If the property doesn't exist or is not available, nothing is inserted, unless a fallback is specified as in %{prop:fallback text}. %% Insert the character '%'. The strings inserted by format specifiers will be checked for characters not allowed in filenames (including '/' and '\'), and replaced with the placeholder '_'. (This doesn't happen for text that was passed with the --screenshot-template option, and allows specifying a screenshot target directory by prefixing the template with a relative or absolute path.)
-rw-r--r--cfg-mplayer.h1
-rw-r--r--mplayer.c1
-rw-r--r--options.h1
-rw-r--r--screenshot.c250
-rw-r--r--screenshot.h3
5 files changed, 225 insertions, 31 deletions
diff --git a/cfg-mplayer.h b/cfg-mplayer.h
index 0664d004a1..a8f485c7c9 100644
--- a/cfg-mplayer.h
+++ b/cfg-mplayer.h
@@ -938,6 +938,7 @@ const m_option_t mplayer_opts[]={
OPT_INTRANGE("screenshot-jpeg-quality", screenshot_jpeg_quality, 0, 0, 100),
OPT_INTRANGE("screenshot-png-compression", screenshot_png_compression, 0, 0, 9),
OPT_STRING("screenshot-filetype", screenshot_filetype, 0),
+ OPT_STRING("screenshot-template", screenshot_template, 0),
OPT_FLAG_ON("list-properties", list_properties, CONF_GLOBAL),
{"identify", &mp_msg_levels[MSGT_IDENTIFY], CONF_TYPE_FLAG, CONF_GLOBAL, 0, MSGL_V, NULL},
diff --git a/mplayer.c b/mplayer.c
index 490f1c7e35..98cc511878 100644
--- a/mplayer.c
+++ b/mplayer.c
@@ -3975,6 +3975,7 @@ int main(int argc, char *argv[])
mp_msg_init();
init_libav();
+ screenshot_init(mpctx);
#ifdef CONFIG_X11
mpctx->x11_state = vo_x11_init_state();
diff --git a/options.h b/options.h
index 670c3ac6e5..f667031595 100644
--- a/options.h
+++ b/options.h
@@ -86,6 +86,7 @@ typedef struct MPOpts {
int screenshot_jpeg_quality;
int screenshot_png_compression;
char *screenshot_filetype;
+ char *screenshot_template;
int audio_output_channels;
int audio_output_format;
diff --git a/screenshot.c b/screenshot.c
index 614f1f55ae..9835d1e810 100644
--- a/screenshot.c
+++ b/screenshot.c
@@ -21,6 +21,7 @@
#include <string.h>
#include <inttypes.h>
#include <setjmp.h>
+#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -38,7 +39,10 @@
#include "talloc.h"
#include "screenshot.h"
#include "mp_core.h"
+#include "m_property.h"
+#include "bstr.h"
#include "mp_msg.h"
+#include "metadata.h"
#include "libmpcodecs/img_format.h"
#include "libmpcodecs/mp_image.h"
#include "libmpcodecs/dec_video.h"
@@ -59,23 +63,20 @@ typedef struct screenshot_ctx {
int using_vf_screenshot;
int frameno;
- char fname[102];
} screenshot_ctx;
struct img_writer {
const char *file_ext;
- int (*write)(screenshot_ctx *ctx, mp_image_t *image);
+ int (*write)(screenshot_ctx *ctx, mp_image_t *image, char *filename);
};
-static screenshot_ctx *screenshot_get_ctx(MPContext *mpctx)
+void screenshot_init(struct MPContext *mpctx)
{
- if (!mpctx->screenshot_ctx) {
- mpctx->screenshot_ctx = talloc(mpctx, screenshot_ctx);
- *mpctx->screenshot_ctx = (screenshot_ctx) {
- .mpctx = mpctx,
- };
- }
- return mpctx->screenshot_ctx;
+ mpctx->screenshot_ctx = talloc(mpctx, screenshot_ctx);
+ *mpctx->screenshot_ctx = (screenshot_ctx) {
+ .mpctx = mpctx,
+ .frameno = 1,
+ };
}
static FILE *open_file(screenshot_ctx *ctx, char *fname) {
@@ -86,9 +87,9 @@ static FILE *open_file(screenshot_ctx *ctx, char *fname) {
return fp;
}
-static int write_png(screenshot_ctx *ctx, struct mp_image *image)
+static int write_png(screenshot_ctx *ctx, struct mp_image *image,
+ char *filename)
{
- char *fname = ctx->fname;
FILE *fp = NULL;
void *outbuffer = NULL;
int success = 0;
@@ -99,7 +100,7 @@ static int write_png(screenshot_ctx *ctx, struct mp_image *image)
if (avcodec_open(avctx, avcodec_find_encoder(CODEC_ID_PNG))) {
mp_msg(MSGT_CPLAYER, MSGL_INFO, "Could not open libavcodec PNG encoder"
- " for saving screenshot\n");
+ " for saving screenshot!\n");
goto error_exit;
}
@@ -120,7 +121,7 @@ static int write_png(screenshot_ctx *ctx, struct mp_image *image)
if (size < 1)
goto error_exit;
- fp = open_file(ctx, fname);
+ fp = open_file(ctx, filename);
if (fp == NULL)
goto error_exit;
@@ -151,11 +152,11 @@ static void write_jpeg_error_exit(j_common_ptr cinfo)
longjmp(*(jmp_buf*)cinfo->client_data, 1);
}
-static int write_jpeg(screenshot_ctx *ctx, mp_image_t *image)
+static int write_jpeg(screenshot_ctx *ctx, mp_image_t *image, char *filename)
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
- FILE *outfile = open_file(ctx, ctx->fname);
+ FILE *outfile = open_file(ctx, filename);
if (!outfile)
return 0;
@@ -186,7 +187,8 @@ static int write_jpeg(screenshot_ctx *ctx, mp_image_t *image)
while (cinfo.next_scanline < cinfo.image_height) {
JSAMPROW row_pointer[1];
- row_pointer[0] = image->planes[0] + cinfo.next_scanline * image->stride[0];
+ row_pointer[0] = image->planes[0] +
+ cinfo.next_scanline * image->stride[0];
jpeg_write_scanlines(&cinfo, row_pointer,1);
}
@@ -230,23 +232,204 @@ static int fexists(char *fname)
return 0;
}
-static void gen_fname(screenshot_ctx *ctx, const char *ext)
+static char *stripext(void *talloc_ctx, const char *s)
{
- do {
- snprintf(ctx->fname, 100, "shot%04d.%s", ++ctx->frameno, ext);
- } while (fexists(ctx->fname) && ctx->frameno < 100000);
- if (fexists(ctx->fname)) {
- ctx->fname[0] = '\0';
- return;
+ const char *end = strrchr(s, '.');
+ if (!end)
+ end = s + strlen(s);
+ return talloc_asprintf(talloc_ctx, "%.*s", end - s, s);
+}
+
+static char *format_time(void *talloc_ctx, double time, bool sub_seconds)
+{
+ int h, m, s = time;
+ h = s / 3600;
+ s -= h * 3600;
+ m = s / 60;
+ s -= m * 60;
+ char *res = talloc_asprintf(talloc_ctx, "%02d:%02d:%02d", h, m, s);
+ if (sub_seconds)
+ res = talloc_asprintf_append(res, ".%03d",
+ (int)((time - (int)time) * 1000));
+ return res;
+}
+
+static char *do_format_property(struct MPContext *mpctx, struct bstr s) {
+ struct bstr prop_name = s;
+ int fallbackpos = bstrchr(s, ':');
+ if (fallbackpos >= 0)
+ prop_name = bstr_splice(prop_name, 0, fallbackpos);
+ char *pn = bstrdup0(NULL, prop_name);
+ char *res = mp_property_print(pn, mpctx);
+ talloc_free(pn);
+ if (!res && fallbackpos >= 0)
+ res = bstrdup0(NULL, bstr_cut(s, fallbackpos + 1));
+ return res;
+}
+
+#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;
+}
- mp_msg(MSGT_CPLAYER, MSGL_INFO, "*** screenshot '%s' ***\n", ctx->fname);
+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 = get_metadata(mpctx, META_NAME);
+ if (video_file) {
+ char *name = video_file;
+ if (fmt == 'F')
+ name = stripext(res, video_file);
+ append_filename(&res, name);
+ }
+ talloc_free(video_file);
+ break;
+ }
+ case 'p':
+ case 'P':
+ append_filename(&res,
+ format_time(res, get_current_time(mpctx), fmt == 'P'));
+ 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(bstr(template), 0, end - template);
+ template = end + 1;
+ char *s = do_format_property(mpctx, prop);
+ if (s)
+ append_filename(&res, s);
+ talloc_free(s);
+ 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)
+{
+ int sequence = 0;
+ for (;;) {
+ int prev_sequence = sequence;
+ char *fname = create_fname(ctx->mpctx,
+ ctx->mpctx->opts.screenshot_template,
+ get_writer(ctx)->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 (!fexists(fname))
+ return fname;
+
+ talloc_free(fname);
+
+ if (sequence == prev_sequence) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "Can't save screenshot, file "
+ "already exists!\n");
+ return NULL;
+ }
+ }
}
void screenshot_save(struct MPContext *mpctx, struct mp_image *image)
{
- screenshot_ctx *ctx = screenshot_get_ctx(mpctx);
+ screenshot_ctx *ctx = mpctx->screenshot_ctx;
const struct img_writer *writer = get_writer(ctx);
struct mp_image *dst = alloc_mpi(image->w, image->h, IMGFMT_RGB24);
@@ -266,8 +449,13 @@ void screenshot_save(struct MPContext *mpctx, struct mp_image *image)
sws_scale(sws, (const uint8_t **)image->planes, image->stride, 0,
image->height, dst->planes, dst->stride);
- gen_fname(ctx, writer->file_ext);
- writer->write(ctx, dst);
+ char *filename = gen_fname(ctx);
+ if (filename) {
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "*** screenshot '%s' ***\n", filename);
+ if (!writer->write(ctx, dst, filename))
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "Error writing screenshot!\n");
+ talloc_free(filename);
+ }
sws_freeContext(sws);
free_mp_image(dst);
@@ -276,7 +464,7 @@ void screenshot_save(struct MPContext *mpctx, struct mp_image *image)
static void vf_screenshot_callback(void *pctx, struct mp_image *image)
{
struct MPContext *mpctx = (struct MPContext *)pctx;
- screenshot_ctx *ctx = screenshot_get_ctx(mpctx);
+ screenshot_ctx *ctx = mpctx->screenshot_ctx;
screenshot_save(mpctx, image);
if (ctx->each_frame)
screenshot_request(mpctx, 0, ctx->full_window);
@@ -299,7 +487,7 @@ void screenshot_request(struct MPContext *mpctx, bool each_frame,
bool full_window)
{
if (mpctx->video_out && mpctx->video_out->config_ok) {
- screenshot_ctx *ctx = screenshot_get_ctx(mpctx);
+ screenshot_ctx *ctx = mpctx->screenshot_ctx;
ctx->using_vf_screenshot = 0;
@@ -335,7 +523,7 @@ void screenshot_request(struct MPContext *mpctx, bool each_frame,
void screenshot_flip(struct MPContext *mpctx)
{
- screenshot_ctx *ctx = screenshot_get_ctx(mpctx);
+ screenshot_ctx *ctx = mpctx->screenshot_ctx;
if (!ctx->each_frame)
return;
diff --git a/screenshot.h b/screenshot.h
index c57778c0b3..6d205990f8 100644
--- a/screenshot.h
+++ b/screenshot.h
@@ -24,6 +24,9 @@
struct MPContext;
struct mp_image;
+// One time initialization at program start.
+void screenshot_init(struct MPContext *mpctx);
+
// Request a taking & saving a screenshot of the currently displayed frame.
// each_frame: If set, this toggles per-frame screenshots, exactly like the
// screenshot slave command (MP_CMD_SCREENSHOT).