ohmu
·
2026-10-15
1#include "media.h"
2#include "common/FFcache.h"
3#include "common/io.h"
4
5void ffDetectMediaImpl(FFMediaResult* media, bool saveCover);
6
7static FFMediaResult result;
8
9// The cover file of this run, if there is one. It is tracked here rather than in `result` because the
10// cached result is dropped between `--dynamic-interval` rounds, and `result.cover` — the only other
11// copy of the path — is destroyed with it. The file itself has to outlive that, since the
12// `kitty-direct` logo type hands its path to the terminal, so it is kept for the whole run and
13// removed at exit.
14static FFstrbuf coverFile;
15static bool coverFileRegistered = false;
16
17static void removeMediaCoverFile(void) {
18 if (coverFile.length > 0) {
19 ffRemoveFile(coverFile.chars);
20 ffStrbufDestroy(&coverFile);
21 }
22}
23
24static void destroyMediaResult(void* storage) {
25 FFMediaResult* media = storage;
26
27 ffStrbufDestroy(&media->error);
28 ffStrbufDestroy(&media->playerId);
29 ffStrbufDestroy(&media->player);
30 ffStrbufDestroy(&media->song);
31 ffStrbufDestroy(&media->artist);
32 ffStrbufDestroy(&media->album);
33 ffStrbufDestroy(&media->url);
34 ffStrbufDestroy(&media->status);
35 ffStrbufDestroy(&media->cover);
36 media->length = 0;
37 media->position = 0;
38 media->removeCoverAfterUse = false;
39}
40
41// `saveCover` asks for a side effect and is not part of the cache identity, so the entry cannot be
42// initialized through `init(void*)`: the first caller of a generation decides, and the callers that
43// follow reuse its result, as they did when the result was cached for the whole run.
44static FFcacheEntry ffCacheEntryMedia = {
45 .name = "media",
46 .storage = &result,
47 .destroy = destroyMediaResult,
48};
49
50const FFMediaResult* ffDetectMedia(bool saveCover) {
51 if (ffCacheBeginInit(&ffCacheEntryMedia)) {
52 ffStrbufInit(&result.error);
53 ffStrbufInit(&result.playerId);
54 ffStrbufInit(&result.player);
55 ffStrbufInit(&result.song);
56 ffStrbufInit(&result.artist);
57 ffStrbufInit(&result.album);
58 ffStrbufInit(&result.url);
59 ffStrbufInit(&result.status);
60 ffStrbufInit(&result.cover);
61 result.length = 0;
62 result.position = 0;
63 result.removeCoverAfterUse = false;
64 ffDetectMediaImpl(&result, saveCover);
65
66 if (result.song.length == 0 && result.error.length == 0) {
67 ffStrbufAppendS(&result.error, "No media found");
68 }
69 ffStrbufTrimRightSpace(&result.song);
70 ffStrbufTrimRightSpace(&result.artist);
71 ffStrbufTrimRightSpace(&result.album);
72 ffStrbufTrimRightSpace(&result.player);
73
74 if (saveCover && result.removeCoverAfterUse) {
75 // A new round can produce a new cover while the previous file is still on disk, and
76 // `coverFile` is about to forget its path. Remove it here, otherwise only the last one
77 // would ever be cleaned up by `atexit`.
78 if (coverFile.length > 0 && !ffStrbufEqual(&coverFile, &result.cover)) {
79 ffRemoveFile(coverFile.chars);
80 }
81 ffStrbufSet(&coverFile, &result.cover);
82 if (!coverFileRegistered) {
83 coverFileRegistered = true;
84 atexit(removeMediaCoverFile);
85 }
86 }
87 }
88
89 return &result;
90}