diff --git a/DOCS/interface-changes/watch-history.txt b/DOCS/interface-changes/watch-history.txt new file mode 100644 index 0000000000000..28e5c674f816b --- /dev/null +++ b/DOCS/interface-changes/watch-history.txt @@ -0,0 +1 @@ +add `--save-watch-history` and `--watch-history-path` options diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 4e932f1eff69c..b9d840a708337 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -1148,6 +1148,28 @@ Watch Later Ignore path (i.e. use filename only) when using watch later feature. (Default: disabled) +Watch History +------------- + +``--save-watch-history`` + Whether to save which files are played. + + .. warning:: + + This option may expose privacy-sensitive information and is thus + disabled by default. + +``--watch-history-path=`` + The path in which to store the watch history. Default: + ``~~state/watch_history.jsonl`` (see `PATHS`_). + + This file contains one JSON array per line. The first field is the UNIX + timestamp when the file was opened, the second field is its normalized path, + and the third file is its title, or ``null`` if no title was present. So + each line is like ``[timestamp, "file_path", "title"]``. + + Any new fields will be appended after the last one. + Video ----- diff --git a/options/options.c b/options/options.c index 3bb958c49a042..2d7cfce922a10 100644 --- a/options/options.c +++ b/options/options.c @@ -811,6 +811,9 @@ static const m_option_t mp_opts[] = { {"watch-later-directory", OPT_ALIAS("watch-later-dir")}, {"watch-later-options", OPT_STRINGLIST(watch_later_options)}, + {"save-watch-history", OPT_BOOL(save_watch_history)}, + {"watch-history-path", OPT_STRING(watch_history_path), .flags = M_OPT_FILE}, + {"ordered-chapters", OPT_BOOL(ordered_chapters)}, {"ordered-chapters-files", OPT_STRING(ordered_chapters_files), .flags = M_OPT_FILE}, @@ -986,6 +989,7 @@ static const struct MPOpts mp_default_opts = { .sync_max_factor = 5, .load_config = true, .position_resume = true, + .watch_history_path = "~~state/watch_history.jsonl", .autoload_files = true, .demuxer_thread = true, .demux_termination_timeout = 0.1, diff --git a/options/options.h b/options/options.h index 3f184c49cf5a9..ef8c4306845db 100644 --- a/options/options.h +++ b/options/options.h @@ -276,6 +276,8 @@ typedef struct MPOpts { bool ignore_path_in_watch_later_config; char *watch_later_dir; char **watch_later_options; + bool save_watch_history; + char *watch_history_path; bool pause; int keep_open; bool keep_open_pause; diff --git a/player/loadfile.c b/player/loadfile.c index 4f204c20e75fa..c284b02fa24c7 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -19,6 +19,7 @@ #include #include #include +#include #include @@ -44,6 +45,7 @@ #include "common/encode.h" #include "common/stats.h" #include "input/input.h" +#include "misc/json.h" #include "misc/language.h" #include "audio/out/ao.h" @@ -1521,6 +1523,44 @@ static void load_external_opts(struct MPContext *mpctx) mp_waiter_wait(&wait); } +static void append_to_watch_history(struct MPContext *mpctx) +{ + if (!mpctx->opts->save_watch_history) + return; + + void *ctx = talloc_new(NULL); + char *history_path = mp_get_user_path(ctx, mpctx->global, + mpctx->opts->watch_history_path); + FILE *history_file = fopen(history_path, "ab"); + + if (!history_file) { + MP_ERR(mpctx, "Failed to write to %s: %s\n", history_path, + mp_strerror(errno)); + goto done; + } + + char *title = (char *)find_non_filename_media_title(mpctx); + mpv_node items[] = { + {.format = MPV_FORMAT_INT64, .u = {.int64 = time(NULL)}}, + {.format = MPV_FORMAT_STRING, .u = {.string = mp_normalize_path(ctx, mpctx->filename)}}, + {.format = title ? MPV_FORMAT_STRING : MPV_FORMAT_NONE, .u = {.string = title}} + }; + mpv_node_list list = {.values = items, .num = 3}; + mpv_node node = {.format = MPV_FORMAT_NODE_ARRAY, .u = {.list = &list}}; + char *dst = talloc_strdup(ctx, ""); + json_write(&dst, &node); + dst = talloc_strdup_append(dst, "\n"); + + bool fail = fwrite(dst, strlen(dst), 1, history_file) != 1; + if (fclose(history_file) || fail) { + MP_ERR(mpctx, "Failed to write to %s: %s\n", history_path, + mp_strerror(errno)); + } + +done: + talloc_free(ctx); +} + // Start playing the current playlist entry. // Handle initialization and deinitialization. static void play_current_file(struct MPContext *mpctx) @@ -1772,6 +1812,8 @@ static void play_current_file(struct MPContext *mpctx) if (watch_later) mp_delete_watch_later_conf(mpctx, mpctx->filename); + append_to_watch_history(mpctx); + if (mpctx->max_frames == 0) { if (!mpctx->stop_play) mpctx->stop_play = PT_NEXT_ENTRY;