1 /*
2 * This file is part of mpv.
3 *
4 * mpv is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * mpv is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <errno.h>
19 #include <stddef.h>
20 #include <stdbool.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 #include <unistd.h>
25 #include <utime.h>
26
27 #include <libavutil/md5.h>
28
29 #include "config.h"
30 #include "mpv_talloc.h"
31
32 #include "osdep/io.h"
33
34 #include "common/global.h"
35 #include "common/encode.h"
36 #include "common/msg.h"
37 #include "misc/ctype.h"
38 #include "options/path.h"
39 #include "options/m_config.h"
40 #include "options/m_config_frontend.h"
41 #include "options/parse_configfile.h"
42 #include "common/playlist.h"
43 #include "options/options.h"
44 #include "options/m_property.h"
45
46 #include "stream/stream.h"
47
48 #include "core.h"
49 #include "command.h"
50
load_all_cfgfiles(struct MPContext * mpctx,char * section,char * filename)51 static void load_all_cfgfiles(struct MPContext *mpctx, char *section,
52 char *filename)
53 {
54 char **cf = mp_find_all_config_files(NULL, mpctx->global, filename);
55 for (int i = 0; cf && cf[i]; i++)
56 m_config_parse_config_file(mpctx->mconfig, cf[i], section, 0);
57 talloc_free(cf);
58 }
59
60 // This name is used in builtin.conf to force encoding defaults (like ao/vo).
61 #define SECT_ENCODE "encoding"
62
mp_parse_cfgfiles(struct MPContext * mpctx)63 void mp_parse_cfgfiles(struct MPContext *mpctx)
64 {
65 struct MPOpts *opts = mpctx->opts;
66
67 mp_mk_config_dir(mpctx->global, "");
68
69 char *p1 = mp_get_user_path(NULL, mpctx->global, "~~home/");
70 char *p2 = mp_get_user_path(NULL, mpctx->global, "~~old_home/");
71 if (strcmp(p1, p2) != 0 && mp_path_exists(p2)) {
72 MP_WARN(mpctx, "Warning, two config dirs found:\n %s (main)\n"
73 " %s (bogus)\nYou should merge or delete the second one.\n",
74 p1, p2);
75 }
76 talloc_free(p1);
77 talloc_free(p2);
78
79 char *section = NULL;
80 bool encoding = opts->encode_opts &&
81 opts->encode_opts->file && opts->encode_opts->file[0];
82 // In encoding mode, we don't want to apply normal config options.
83 // So we "divert" normal options into a separate section, and the diverted
84 // section is never used - unless maybe it's explicitly referenced from an
85 // encoding profile.
86 if (encoding)
87 section = "playback-default";
88
89 load_all_cfgfiles(mpctx, NULL, "encoding-profiles.conf");
90
91 load_all_cfgfiles(mpctx, section, "mpv.conf|config");
92
93 if (encoding)
94 m_config_set_profile(mpctx->mconfig, SECT_ENCODE, 0);
95 }
96
try_load_config(struct MPContext * mpctx,const char * file,int flags,int msgl)97 static int try_load_config(struct MPContext *mpctx, const char *file, int flags,
98 int msgl)
99 {
100 if (!mp_path_exists(file))
101 return 0;
102 MP_MSG(mpctx, msgl, "Loading config '%s'\n", file);
103 m_config_parse_config_file(mpctx->mconfig, file, NULL, flags);
104 return 1;
105 }
106
107 // Set options file-local, and don't set them if the user set them via the
108 // command line.
109 #define FILE_LOCAL_FLAGS (M_SETOPT_BACKUP | M_SETOPT_PRESERVE_CMDLINE)
110
mp_load_per_file_config(struct MPContext * mpctx)111 static void mp_load_per_file_config(struct MPContext *mpctx)
112 {
113 struct MPOpts *opts = mpctx->opts;
114 char *confpath;
115 char cfg[512];
116 const char *file = mpctx->filename;
117
118 if (opts->use_filedir_conf) {
119 if (snprintf(cfg, sizeof(cfg), "%s.conf", file) >= sizeof(cfg)) {
120 MP_VERBOSE(mpctx, "Filename is too long, can not load file or "
121 "directory specific config files\n");
122 return;
123 }
124
125 char *name = mp_basename(cfg);
126
127 bstr dir = mp_dirname(cfg);
128 char *dircfg = mp_path_join_bstr(NULL, dir, bstr0("mpv.conf"));
129 try_load_config(mpctx, dircfg, FILE_LOCAL_FLAGS, MSGL_INFO);
130 talloc_free(dircfg);
131
132 if (try_load_config(mpctx, cfg, FILE_LOCAL_FLAGS, MSGL_INFO))
133 return;
134
135 if ((confpath = mp_find_config_file(NULL, mpctx->global, name))) {
136 try_load_config(mpctx, confpath, FILE_LOCAL_FLAGS, MSGL_INFO);
137
138 talloc_free(confpath);
139 }
140 }
141 }
142
mp_auto_load_profile(struct MPContext * mpctx,char * category,bstr item)143 static void mp_auto_load_profile(struct MPContext *mpctx, char *category,
144 bstr item)
145 {
146 if (!item.len)
147 return;
148
149 char t[512];
150 snprintf(t, sizeof(t), "%s.%.*s", category, BSTR_P(item));
151 m_profile_t *p = m_config_get_profile0(mpctx->mconfig, t);
152 if (p) {
153 MP_INFO(mpctx, "Auto-loading profile '%s'\n", t);
154 m_config_set_profile(mpctx->mconfig, t, FILE_LOCAL_FLAGS);
155 }
156 }
157
mp_load_auto_profiles(struct MPContext * mpctx)158 void mp_load_auto_profiles(struct MPContext *mpctx)
159 {
160 mp_auto_load_profile(mpctx, "protocol",
161 mp_split_proto(bstr0(mpctx->filename), NULL));
162 mp_auto_load_profile(mpctx, "extension",
163 bstr0(mp_splitext(mpctx->filename, NULL)));
164
165 mp_load_per_file_config(mpctx);
166 }
167
168 #define MP_WATCH_LATER_CONF "watch_later"
169
check_mtime(const char * f1,const char * f2)170 static bool check_mtime(const char *f1, const char *f2)
171 {
172 struct stat st1, st2;
173 if (stat(f1, &st1) != 0 || stat(f2, &st2) != 0)
174 return false;
175 return st1.st_mtime == st2.st_mtime;
176 }
177
copy_mtime(const char * f1,const char * f2)178 static bool copy_mtime(const char *f1, const char *f2)
179 {
180 struct stat st1, st2;
181
182 if (stat(f1, &st1) != 0 || stat(f2, &st2) != 0)
183 return false;
184
185 struct utimbuf ut = {
186 .actime = st2.st_atime, // we want to pass this through intact
187 .modtime = st1.st_mtime,
188 };
189
190 if (utime(f2, &ut) != 0)
191 return false;
192
193 return true;
194 }
195
mp_get_playback_resume_config_filename(struct MPContext * mpctx,const char * fname)196 static char *mp_get_playback_resume_config_filename(struct MPContext *mpctx,
197 const char *fname)
198 {
199 struct MPOpts *opts = mpctx->opts;
200 char *res = NULL;
201 void *tmp = talloc_new(NULL);
202 const char *realpath = fname;
203 bstr bfname = bstr0(fname);
204 if (!mp_is_url(bfname)) {
205 if (opts->ignore_path_in_watch_later_config) {
206 realpath = mp_basename(fname);
207 } else {
208 char *cwd = mp_getcwd(tmp);
209 if (!cwd)
210 goto exit;
211 realpath = mp_path_join(tmp, cwd, fname);
212 }
213 }
214 uint8_t md5[16];
215 av_md5_sum(md5, realpath, strlen(realpath));
216 char *conf = talloc_strdup(tmp, "");
217 for (int i = 0; i < 16; i++)
218 conf = talloc_asprintf_append(conf, "%02X", md5[i]);
219
220 if (!mpctx->cached_watch_later_configdir) {
221 char *wl_dir = mpctx->opts->watch_later_directory;
222 if (wl_dir && wl_dir[0]) {
223 mpctx->cached_watch_later_configdir =
224 mp_get_user_path(mpctx, mpctx->global, wl_dir);
225 }
226 }
227
228 if (!mpctx->cached_watch_later_configdir) {
229 mpctx->cached_watch_later_configdir =
230 mp_find_user_config_file(mpctx, mpctx->global, MP_WATCH_LATER_CONF);
231 }
232
233 if (mpctx->cached_watch_later_configdir)
234 res = mp_path_join(NULL, mpctx->cached_watch_later_configdir, conf);
235
236 exit:
237 talloc_free(tmp);
238 return res;
239 }
240
241 // Should follow what parser-cfg.c does/needs
needs_config_quoting(const char * s)242 static bool needs_config_quoting(const char *s)
243 {
244 if (s[0] == '%')
245 return true;
246 for (int i = 0; s[i]; i++) {
247 unsigned char c = s[i];
248 if (!mp_isprint(c) || mp_isspace(c) || c == '#' || c == '\'' || c == '"')
249 return true;
250 }
251 return false;
252 }
253
write_filename(struct MPContext * mpctx,FILE * file,char * filename)254 static void write_filename(struct MPContext *mpctx, FILE *file, char *filename)
255 {
256 if (mpctx->opts->write_filename_in_watch_later_config) {
257 char write_name[1024] = {0};
258 for (int n = 0; filename[n] && n < sizeof(write_name) - 1; n++)
259 write_name[n] = (unsigned char)filename[n] < 32 ? '_' : filename[n];
260 fprintf(file, "# %s\n", write_name);
261 }
262 }
263
write_redirect(struct MPContext * mpctx,char * path)264 static void write_redirect(struct MPContext *mpctx, char *path)
265 {
266 char *conffile = mp_get_playback_resume_config_filename(mpctx, path);
267 if (conffile) {
268 FILE *file = fopen(conffile, "wb");
269 if (file) {
270 fprintf(file, "# redirect entry\n");
271 write_filename(mpctx, file, path);
272 fclose(file);
273 }
274
275 if (mpctx->opts->position_check_mtime &&
276 !mp_is_url(bstr0(path)) && !copy_mtime(path, conffile))
277 MP_WARN(mpctx, "Can't copy mtime from %s to %s\n", path, conffile);
278
279 talloc_free(conffile);
280 }
281 }
282
mp_write_watch_later_conf(struct MPContext * mpctx)283 void mp_write_watch_later_conf(struct MPContext *mpctx)
284 {
285 struct playlist_entry *cur = mpctx->playing;
286 char *conffile = NULL;
287 if (!cur)
288 goto exit;
289
290 struct demuxer *demux = mpctx->demuxer;
291
292 conffile = mp_get_playback_resume_config_filename(mpctx, cur->filename);
293 if (!conffile)
294 goto exit;
295
296 mp_mk_config_dir(mpctx->global, mpctx->cached_watch_later_configdir);
297
298 MP_INFO(mpctx, "Saving state.\n");
299
300 FILE *file = fopen(conffile, "wb");
301 if (!file)
302 goto exit;
303
304 write_filename(mpctx, file, cur->filename);
305
306 double pos = get_current_time(mpctx);
307
308 if ((demux && (!demux->seekable || demux->partially_seekable)) ||
309 pos == MP_NOPTS_VALUE)
310 {
311 MP_INFO(mpctx, "Not seekable, or time unknown - not saving position.\n");
312 } else {
313 fprintf(file, "start=%f\n", pos);
314 }
315 char **watch_later_options = mpctx->opts->watch_later_options;
316 for (int i = 0; watch_later_options && watch_later_options[i]; i++) {
317 char *pname = watch_later_options[i];
318 // Only store it if it's different from the initial value.
319 if (m_config_watch_later_backup_opt_changed(mpctx->mconfig, pname)) {
320 char *val = NULL;
321 mp_property_do(pname, M_PROPERTY_GET_STRING, &val, mpctx);
322 if (needs_config_quoting(val)) {
323 // e.g. '%6%STRING'
324 fprintf(file, "%s=%%%d%%%s\n", pname, (int)strlen(val), val);
325 } else {
326 fprintf(file, "%s=%s\n", pname, val);
327 }
328 talloc_free(val);
329 }
330 }
331 fclose(file);
332
333 if (mpctx->opts->position_check_mtime &&
334 !mp_is_url(bstr0(cur->filename)) &&
335 !copy_mtime(cur->filename, conffile))
336 {
337 MP_WARN(mpctx, "Can't copy mtime from %s to %s\n", cur->filename,
338 conffile);
339 }
340
341 // This allows us to recursively resume directories etc., whose entries are
342 // expanded the first time it's "played". For example, if "/a/b/c.mkv" is
343 // the current entry, then we want to resume this file if the user does
344 // "mpv /a". This would expand to the directory entries in "/a", and if
345 // "/a/a.mkv" is not the first entry, this would be played.
346 // Here, we write resume entries for "/a" and "/a/b".
347 // (Unfortunately, this will leave stray resume files on resume, because
348 // obviously it resumes only from one of those paths.)
349 for (int n = 0; n < cur->num_redirects; n++)
350 write_redirect(mpctx, cur->redirects[n]);
351 // And at last, for local directories, we write an entry for each path
352 // prefix, so the user can resume from an arbitrary directory. This starts
353 // with the first redirect (all other redirects are further prefixes).
354 if (cur->num_redirects) {
355 char *path = cur->redirects[0];
356 char tmp[4096];
357 if (!mp_is_url(bstr0(path)) && strlen(path) < sizeof(tmp)) {
358 snprintf(tmp, sizeof(tmp), "%s", path);
359 for (;;) {
360 bstr dir = mp_dirname(tmp);
361 if (dir.len == strlen(tmp) || !dir.len || bstr_equals0(dir, "."))
362 break;
363
364 tmp[dir.len] = '\0';
365 if (strlen(tmp) >= 2) // keep "/"
366 mp_path_strip_trailing_separator(tmp);
367 write_redirect(mpctx, tmp);
368 }
369 }
370 }
371
372 exit:
373 talloc_free(conffile);
374 }
375
mp_delete_watch_later_conf(struct MPContext * mpctx,const char * file)376 void mp_delete_watch_later_conf(struct MPContext *mpctx, const char *file)
377 {
378 if (!file) {
379 struct playlist_entry *cur = mpctx->playing;
380 if (!cur)
381 return;
382 file = cur->filename;
383 if (!file)
384 return;
385 }
386
387 char *fname = mp_get_playback_resume_config_filename(mpctx, file);
388 if (fname)
389 unlink(fname);
390 talloc_free(fname);
391 }
392
mp_load_playback_resume(struct MPContext * mpctx,const char * file)393 void mp_load_playback_resume(struct MPContext *mpctx, const char *file)
394 {
395 if (!mpctx->opts->position_resume)
396 return;
397 char *fname = mp_get_playback_resume_config_filename(mpctx, file);
398 if (fname && mp_path_exists(fname)) {
399 if (mpctx->opts->position_check_mtime &&
400 !mp_is_url(bstr0(file)) && !check_mtime(file, fname))
401 {
402 talloc_free(fname);
403 return;
404 }
405
406 // Never apply the saved start position to following files
407 m_config_backup_opt(mpctx->mconfig, "start");
408 MP_INFO(mpctx, "Resuming playback. This behavior can "
409 "be disabled with --no-resume-playback.\n");
410 try_load_config(mpctx, fname, M_SETOPT_PRESERVE_CMDLINE, MSGL_V);
411 unlink(fname);
412 }
413 talloc_free(fname);
414 }
415
416 // Returns the first file that has a resume config.
417 // Compared to hashing the playlist file or contents and managing separate
418 // resume file for them, this is simpler, and also has the nice property
419 // that appending to a playlist doesn't interfere with resuming (especially
420 // if the playlist comes from the command line).
mp_check_playlist_resume(struct MPContext * mpctx,struct playlist * playlist)421 struct playlist_entry *mp_check_playlist_resume(struct MPContext *mpctx,
422 struct playlist *playlist)
423 {
424 if (!mpctx->opts->position_resume)
425 return NULL;
426 for (int n = 0; n < playlist->num_entries; n++) {
427 struct playlist_entry *e = playlist->entries[n];
428 char *conf = mp_get_playback_resume_config_filename(mpctx, e->filename);
429 bool exists = conf && mp_path_exists(conf);
430 talloc_free(conf);
431 if (exists)
432 return e;
433 }
434 return NULL;
435 }
436
437