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 036616b
Show file tree
Hide file tree
Showing 5 changed files with 71 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
22 changes: 22 additions & 0 deletions DOCS/man/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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=<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
-----

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
42 changes: 42 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,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)
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 036616b

Please sign in to comment.