Skip to content

Commit

Permalink
loadfile: optionally save the watch history
Browse files Browse the repository at this point in the history
The history could be formatted as CSV, but this requires escaping the
separator in the fields and doesn't work with paths and titles with
newlines, or as JSON, but it is inefficient to reread and rewrite the
whole history on each new file, and doing so overwrites the history with
an empty file when writing without disk space left. So this uses a
hybrid of one JSON array per line to get the best of both worlds. This
is called NDJSON or JSONL.
  • Loading branch information
guidocella committed Jan 9, 2025
1 parent e48c9f4 commit 47e7af2
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 0 deletions.
1 change: 1 addition & 0 deletions DOCS/interface-changes/watch-history.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add `--save-watch-history` and `--watch-history-path` options
19 changes: 19 additions & 0 deletions DOCS/man/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,25 @@ 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=<path>``
The path in which to store the watch history. Default:
``~~state/watch_history.jsonl`` (see `PATHS`_).

This file contains one JSON object per line. Its ``time`` field is the UNIX
timestamp when the file was opened, its ``path`` field is the normalized
path, and its ``title`` field is the title when it was available.

Video
-----

Expand Down
4 changes: 4 additions & 0 deletions options/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions options/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
62 changes: 62 additions & 0 deletions player/loadfile.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <stdbool.h>
#include <inttypes.h>
#include <assert.h>
#include <time.h>

#include <libavutil/avutil.h>

Expand All @@ -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"
Expand Down Expand Up @@ -1521,6 +1523,64 @@ 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_list *list = talloc_zero(ctx, mpv_node_list);
mpv_node node = {
.format = MPV_FORMAT_NODE_MAP,
.u.list = list,
};
list->num = title ? 3 : 2;
list->keys = talloc_array(ctx, char*, list->num);
list->values = talloc_array(ctx, mpv_node, list->num);
list->keys[0] = "time";
list->values[0] = (struct mpv_node) {
.format = MPV_FORMAT_INT64,
.u.int64 = time(NULL),
};
list->keys[1] = "path";
list->values[1] = (struct mpv_node) {
.format = MPV_FORMAT_STRING,
.u.string = mp_normalize_path(ctx, mpctx->filename),
};
if (title) {
list->keys[2] = "title";
list->values[2] = (struct mpv_node) {
.format = MPV_FORMAT_STRING,
.u.string = title,
};
}

char *dst = talloc_strdup(ctx, "");
json_write(&dst, &node);
dst = talloc_strdup_append(dst, "\n");

bool failed = fwrite(dst, strlen(dst), 1, history_file) != 1;
if (fclose(history_file) || failed) {
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)
Expand Down Expand Up @@ -1772,6 +1832,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;
Expand Down

0 comments on commit 47e7af2

Please sign in to comment.