/src/mpv/demux/demux_playlist.c
Line | Count | Source |
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 <stdlib.h> |
19 | | #include <string.h> |
20 | | |
21 | | #include <libavutil/common.h> |
22 | | |
23 | | #include "common/common.h" |
24 | | #include "options/options.h" |
25 | | #include "options/m_config.h" |
26 | | #include "common/msg.h" |
27 | | #include "common/playlist.h" |
28 | | #include "misc/charset_conv.h" |
29 | | #include "misc/thread_tools.h" |
30 | | #include "options/path.h" |
31 | | #include "player/core.h" |
32 | | #include "stream/stream.h" |
33 | | #include "osdep/io.h" |
34 | | #include "misc/natural_sort.h" |
35 | | #include "demux.h" |
36 | | |
37 | | #define PROBE_SIZE (8 * 1024) |
38 | | |
39 | | enum dir_mode { |
40 | | DIR_AUTO, |
41 | | DIR_LAZY, |
42 | | DIR_RECURSIVE, |
43 | | DIR_IGNORE, |
44 | | }; |
45 | | |
46 | | enum autocreate_mode { |
47 | | AUTO_NONE = 0, |
48 | | AUTO_VIDEO = 1 << 0, |
49 | | AUTO_AUDIO = 1 << 1, |
50 | | AUTO_IMAGE = 1 << 2, |
51 | | AUTO_ARCHIVE = 1 << 3, |
52 | | AUTO_PLAYLIST = 1 << 4, |
53 | | AUTO_ANY = 1 << 5, |
54 | | }; |
55 | | |
56 | | #define OPT_BASE_STRUCT struct demux_playlist_opts |
57 | | struct demux_playlist_opts { |
58 | | int dir_mode; |
59 | | }; |
60 | | |
61 | | struct m_sub_options demux_playlist_conf = { |
62 | | .opts = (const struct m_option[]) { |
63 | | {"directory-mode", OPT_CHOICE(dir_mode, |
64 | | {"auto", DIR_AUTO}, |
65 | | {"lazy", DIR_LAZY}, |
66 | | {"recursive", DIR_RECURSIVE}, |
67 | | {"ignore", DIR_IGNORE})}, |
68 | | {0} |
69 | | }, |
70 | | .size = sizeof(struct demux_playlist_opts), |
71 | | .defaults = &(const struct demux_playlist_opts){ |
72 | | .dir_mode = DIR_AUTO, |
73 | | }, |
74 | | .change_flags = UPDATE_DEMUXER, |
75 | | }; |
76 | | |
77 | | static bool check_mimetype(struct stream *s, const char *const *list) |
78 | 621k | { |
79 | 621k | if (s->mime_type) { |
80 | 0 | for (int n = 0; list && list[n]; n++) { |
81 | 0 | if (strcasecmp(s->mime_type, list[n]) == 0) |
82 | 0 | return true; |
83 | 0 | } |
84 | 0 | } |
85 | 621k | return false; |
86 | 621k | } |
87 | | |
88 | | struct pl_parser { |
89 | | struct mpv_global *global; |
90 | | struct mp_log *log; |
91 | | struct stream *s; |
92 | | char buffer[2 * 1024 * 1024]; |
93 | | int utf16; |
94 | | struct playlist *pl; |
95 | | bool error; |
96 | | bool probing; |
97 | | bool force; |
98 | | bool line_allocated; |
99 | | int autocreate_playlist; |
100 | | enum demux_check check_level; |
101 | | struct stream *real_stream; |
102 | | const char *format; |
103 | | char *codepage; |
104 | | char **directory_filter; |
105 | | struct demux_playlist_opts *opts; |
106 | | struct MPOpts *mp_opts; |
107 | | }; |
108 | | |
109 | | |
110 | | static uint16_t stream_read_word_endian(stream_t *s, bool big_endian) |
111 | 1.66M | { |
112 | 1.66M | unsigned int y = stream_read_char(s); |
113 | 1.66M | y = (y << 8) | stream_read_char(s); |
114 | 1.66M | if (!big_endian) |
115 | 302k | y = ((y >> 8) & 0xFF) | (y << 8); |
116 | 1.66M | return y; |
117 | 1.66M | } |
118 | | |
119 | | // Read characters until the next '\n' (including), or until the buffer in s is |
120 | | // exhausted. |
121 | | static int read_characters(stream_t *s, uint8_t *dst, int dstsize, int utf16) |
122 | 1.80M | { |
123 | 1.80M | if (utf16 == 1 || utf16 == 2) { |
124 | 10.0k | uint8_t *cur = dst; |
125 | 1.65M | while (1) { |
126 | 1.65M | if ((cur - dst) + 8 >= dstsize) // PUT_UTF8 writes max. 8 bytes |
127 | 0 | return -1; // line too long |
128 | 1.65M | uint32_t c; |
129 | 1.65M | uint8_t tmp; |
130 | 1.65M | GET_UTF16(c, stream_read_word_endian(s, utf16 == 2), return -1;) |
131 | 1.65M | if (s->eof) |
132 | 7.57k | break; // legitimate EOF; ignore the case of partial reads |
133 | 1.65M | PUT_UTF8(c, tmp, *cur++ = tmp;) |
134 | 1.64M | if (c == '\n') |
135 | 44 | break; |
136 | 1.64M | } |
137 | 7.61k | return cur - dst; |
138 | 1.79M | } else { |
139 | 1.79M | uint8_t buf[1024]; |
140 | 1.79M | int buf_len = stream_read_peek(s, buf, sizeof(buf)); |
141 | 1.79M | uint8_t *end = memchr(buf, '\n', buf_len); |
142 | 1.79M | int len = end ? end - buf + 1 : buf_len; |
143 | 1.79M | if (len > dstsize) |
144 | 0 | return -1; // line too long |
145 | 1.79M | memcpy(dst, buf, len); |
146 | 1.79M | stream_seek_skip(s, stream_tell(s) + len); |
147 | 1.79M | return len; |
148 | 1.79M | } |
149 | 1.80M | } |
150 | | |
151 | | // On error, or if the line is larger than max-1, return NULL and unset s->eof. |
152 | | // On EOF, return NULL, and s->eof will be set. |
153 | | // Otherwise, return the line (including \n or \r\n at the end of the line). |
154 | | // If the return value is non-NULL, it's always the same as mem. |
155 | | // utf16: 0: UTF8 or 8 bit legacy, 1: UTF16-LE, 2: UTF16-BE |
156 | | static char *read_line(stream_t *s, char *mem, int max, int utf16) |
157 | 1.43M | { |
158 | 1.43M | if (max < 1) |
159 | 0 | return NULL; |
160 | 1.43M | int read = 0; |
161 | 1.80M | while (1) { |
162 | | // Reserve 1 byte of ptr for terminating \0. |
163 | 1.80M | int l = read_characters(s, &mem[read], max - read - 1, utf16); |
164 | 1.80M | if (l < 0 || memchr(&mem[read], '\0', l)) { |
165 | 29.9k | MP_WARN(s, "error reading line\n"); |
166 | 29.9k | return NULL; |
167 | 29.9k | } |
168 | 1.77M | read += l; |
169 | 1.77M | if (l == 0 || (read > 0 && mem[read - 1] == '\n')) |
170 | 1.40M | break; |
171 | 1.77M | } |
172 | 1.40M | mem[read] = '\0'; |
173 | 1.40M | if (!stream_read_peek(s, &(char){0}, 1) && read == 0) // legitimate EOF |
174 | 5.79k | return NULL; |
175 | 1.40M | return mem; |
176 | 1.40M | } |
177 | | |
178 | | static char *pl_get_line0(struct pl_parser *p) |
179 | 1.43M | { |
180 | 1.43M | char *res = read_line(p->s, p->buffer, sizeof(p->buffer), p->utf16); |
181 | 1.43M | if (res) { |
182 | 1.40M | int len = strlen(res); |
183 | 1.40M | if (len > 0 && res[len - 1] == '\n') |
184 | 1.21M | res[len - 1] = '\0'; |
185 | 1.40M | } else { |
186 | 35.7k | p->error |= !p->s->eof; |
187 | 35.7k | } |
188 | 1.43M | return res; |
189 | 1.43M | } |
190 | | |
191 | | static bstr pl_get_line(struct pl_parser *p) |
192 | 1.43M | { |
193 | 1.43M | bstr line = bstr_strip(bstr0(pl_get_line0(p))); |
194 | 1.43M | const char *charset = mp_charset_guess(p, p->log, line, p->codepage, 0); |
195 | 1.43M | if (charset && !mp_charset_is_utf8(charset)) { |
196 | 510k | bstr utf8 = mp_iconv_to_utf8(p->log, line, charset, 0); |
197 | 510k | if (utf8.start && utf8.start != line.start) { |
198 | 510k | line = utf8; |
199 | 510k | p->line_allocated = true; |
200 | 510k | } |
201 | 510k | } |
202 | 1.43M | return line; |
203 | 1.43M | } |
204 | | |
205 | | // Helper in case mp_iconv_to_utf8 allocates memory |
206 | | static void pl_free_line(struct pl_parser *p, bstr line) |
207 | 1.38M | { |
208 | 1.38M | if (p->line_allocated) { |
209 | 510k | talloc_free(line.start); |
210 | 510k | p->line_allocated = false; |
211 | 510k | } |
212 | 1.38M | } |
213 | | |
214 | | static void pl_add(struct pl_parser *p, bstr entry) |
215 | 385k | { |
216 | 385k | void *ctx = talloc_new(NULL); |
217 | 385k | char *path = mp_get_user_path(ctx, p->global, bstrto0(ctx, entry)); |
218 | | |
219 | 385k | bstr proto = mp_split_proto(bstr0(p->s->url), NULL); |
220 | | // Don't add base path to self-expanding protocols |
221 | 385k | if (!mp_is_url(entry) && bstrcasecmp0(proto, "memory") && |
222 | 134k | bstrcasecmp0(proto, "lavf") && bstrcasecmp0(proto, "hex") && |
223 | 28.8k | bstrcasecmp0(proto, "data") && bstrcasecmp0(proto, "fd")) { |
224 | 1.18k | const char *base = p->s->effective_url ? p->s->effective_url : p->s->url; |
225 | 1.18k | path = mp_path_join_bstr(ctx, mp_dirname(base), bstr0(path)); |
226 | 1.18k | } |
227 | | |
228 | 385k | playlist_append_file(p->pl, path); |
229 | 385k | talloc_free(ctx); |
230 | 385k | } |
231 | | |
232 | | static bool pl_eof(struct pl_parser *p) |
233 | 842k | { |
234 | 842k | return p->error || p->s->eof; |
235 | 842k | } |
236 | | |
237 | | static bool maybe_text(bstr d) |
238 | 9.76k | { |
239 | 5.56M | for (int n = 0; n < d.len; n++) { |
240 | 5.55M | unsigned char c = d.start[n]; |
241 | 5.55M | if (c < 32 && c != '\n' && c != '\r' && c != '\t') |
242 | 6.24k | return false; |
243 | 5.55M | } |
244 | 3.52k | return true; |
245 | 9.76k | } |
246 | | |
247 | | static int parse_m3u(struct pl_parser *p) |
248 | 154k | { |
249 | 154k | bstr line = pl_get_line(p); |
250 | 154k | if (p->probing && !bstr_equals0(line, "#EXTM3U")) { |
251 | | // Last resort: if the file extension is m3u, it might be headerless. |
252 | 147k | if (p->check_level == DEMUX_CHECK_UNSAFE) { |
253 | 62.5k | bstr ext = mp_get_ext(bstr0(p->real_stream->url)); |
254 | 62.5k | char probe[PROBE_SIZE]; |
255 | 62.5k | int len = stream_read_peek(p->real_stream, probe, sizeof(probe)); |
256 | 62.5k | bstr data = {probe, len}; |
257 | 62.5k | if (ext.len && data.len >= 2 && maybe_text(data)) { |
258 | 3.52k | const char *exts[] = {"m3u", "m3u8", "strm", NULL}; |
259 | 8.60k | for (int n = 0; exts[n]; n++) { |
260 | 7.36k | if (bstrcasecmp(ext, bstr0(exts[n])) == 0) |
261 | 2.29k | goto ok; |
262 | 7.36k | } |
263 | 3.52k | } |
264 | 62.5k | } |
265 | 144k | pl_free_line(p, line); |
266 | 144k | return -1; |
267 | 147k | } |
268 | | |
269 | 9.11k | ok: |
270 | 9.11k | if (p->probing) { |
271 | 4.55k | pl_free_line(p, line); |
272 | 4.55k | return 0; |
273 | 4.55k | } |
274 | | |
275 | 4.55k | char *title = NULL; |
276 | 669k | while (line.len || !pl_eof(p)) { |
277 | 664k | bstr line_dup = line; |
278 | 664k | if (bstr_eatstart0(&line_dup, "#EXTINF:")) { |
279 | 1.90k | bstr duration, btitle; |
280 | 1.90k | if (bstr_split_tok(line_dup, ",", &duration, &btitle) && btitle.len) { |
281 | 313 | talloc_free(title); |
282 | 313 | title = bstrto0(NULL, btitle); |
283 | 313 | } |
284 | 663k | } else if (bstr_startswith0(line_dup, "#EXT-X-")) { |
285 | | // Note that this is only used for user advice. Actual HLS demuxing |
286 | | // must happen through demux_lavf. |
287 | 1.19k | p->format = "hls"; |
288 | 661k | } else if (line_dup.len > 0 && !bstr_startswith0(line_dup, "#")) { |
289 | 382k | pl_add(p, line); |
290 | 382k | struct playlist_entry *e = playlist_get_last(p->pl); |
291 | 382k | e->title = talloc_steal(e, title); |
292 | 382k | title = NULL; |
293 | 382k | } |
294 | 664k | pl_free_line(p, line); |
295 | 664k | line = pl_get_line(p); |
296 | 664k | } |
297 | 4.55k | pl_free_line(p, line); |
298 | 4.55k | talloc_free(title); |
299 | 4.55k | return 0; |
300 | 9.11k | } |
301 | | |
302 | | static int parse_ref_init(struct pl_parser *p) |
303 | 72.6k | { |
304 | 72.6k | bstr line = pl_get_line(p); |
305 | 72.6k | if (!bstr_equals0(line, "[Reference]")) { |
306 | 72.3k | pl_free_line(p, line); |
307 | 72.3k | return -1; |
308 | 72.3k | } |
309 | 284 | pl_free_line(p, line); |
310 | | |
311 | | // ASF http streaming redirection - this is needed because ffmpeg http:// |
312 | | // and mmsh:// can not automatically switch automatically between each |
313 | | // others. Both protocols use http - MMSH requires special http headers |
314 | | // to "activate" it, and will in other cases return this playlist. |
315 | 284 | static const char *const mmsh_types[] = {"audio/x-ms-wax", |
316 | 284 | "audio/x-ms-wma", "video/x-ms-asf", "video/x-ms-afs", "video/x-ms-wmv", |
317 | 284 | "video/x-ms-wma", "application/x-mms-framed", |
318 | 284 | "application/vnd.ms.wms-hdr.asfv1", NULL}; |
319 | 284 | bstr burl = bstr0(p->s->url); |
320 | 284 | if (bstr_eatstart0(&burl, "http://") && check_mimetype(p->s, mmsh_types)) { |
321 | 0 | MP_INFO(p, "Redirecting to mmsh://\n"); |
322 | 0 | playlist_append_file(p->pl, talloc_asprintf(p, "mmsh://%.*s", BSTR_P(burl))); |
323 | 0 | return 0; |
324 | 0 | } |
325 | | |
326 | 72.8k | while (!pl_eof(p)) { |
327 | 72.5k | line = pl_get_line(p); |
328 | 72.5k | bstr value; |
329 | 72.5k | if (bstr_case_startswith(line, bstr0("Ref"))) { |
330 | 1.21k | bstr_split_tok(line, "=", &(bstr){0}, &value); |
331 | 1.21k | if (value.len) |
332 | 638 | pl_add(p, value); |
333 | 1.21k | } |
334 | 72.5k | pl_free_line(p, line); |
335 | 72.5k | } |
336 | 284 | return 0; |
337 | 284 | } |
338 | | |
339 | | static int parse_ini_thing(struct pl_parser *p, const char *header, |
340 | | const char *entry) |
341 | 144k | { |
342 | 144k | bstr line = {0}; |
343 | 343k | while (!line.len && !pl_eof(p)) |
344 | 199k | line = pl_get_line(p); |
345 | 144k | if (bstrcasecmp0(line, header) != 0) { |
346 | 144k | pl_free_line(p, line); |
347 | 144k | return -1; |
348 | 144k | } |
349 | 610 | if (p->probing) { |
350 | 305 | pl_free_line(p, line); |
351 | 305 | return 0; |
352 | 305 | } |
353 | 305 | pl_free_line(p, line); |
354 | 273k | while (!pl_eof(p)) { |
355 | 272k | line = pl_get_line(p); |
356 | 272k | bstr key, value; |
357 | 272k | if (bstr_split_tok(line, "=", &key, &value) && |
358 | 6.06k | bstr_case_startswith(key, bstr0(entry))) |
359 | 2.52k | { |
360 | 2.52k | value = bstr_strip(value); |
361 | 2.52k | if (bstr_startswith0(value, "\"") && bstr_endswith0(value, "\"")) |
362 | 143 | value = bstr_splice(value, 1, -1); |
363 | 2.52k | pl_add(p, value); |
364 | 2.52k | } |
365 | 272k | pl_free_line(p, line); |
366 | 272k | } |
367 | 305 | return 0; |
368 | 610 | } |
369 | | |
370 | | static int parse_pls(struct pl_parser *p) |
371 | 72.5k | { |
372 | 72.5k | return parse_ini_thing(p, "[playlist]", "File"); |
373 | 72.5k | } |
374 | | |
375 | | static int parse_url(struct pl_parser *p) |
376 | 72.2k | { |
377 | 72.2k | return parse_ini_thing(p, "[InternetShortcut]", "URL"); |
378 | 72.2k | } |
379 | | |
380 | | static int parse_txt(struct pl_parser *p) |
381 | 72.0k | { |
382 | 72.0k | if (!p->force) |
383 | 72.0k | return -1; |
384 | 0 | if (p->probing) |
385 | 0 | return 0; |
386 | 0 | MP_WARN(p, "Reading plaintext playlist.\n"); |
387 | 0 | while (!pl_eof(p)) { |
388 | 0 | bstr line = pl_get_line(p); |
389 | 0 | if (line.len == 0) |
390 | 0 | continue; |
391 | 0 | pl_add(p, line); |
392 | 0 | pl_free_line(p, line); |
393 | 0 | } |
394 | 0 | return 0; |
395 | 0 | } |
396 | | |
397 | 0 | #define MAX_DIR_STACK 20 |
398 | | |
399 | | static bool same_st(struct stat *st1, struct stat *st2) |
400 | 0 | { |
401 | 0 | return st1->st_dev == st2->st_dev && st1->st_ino == st2->st_ino; |
402 | 0 | } |
403 | | |
404 | | struct pl_dir_entry { |
405 | | char *path; |
406 | | char *name; |
407 | | struct stat st; |
408 | | bool is_dir; |
409 | | }; |
410 | | |
411 | | static int cmp_dir_entry(const void *a, const void *b) |
412 | 0 | { |
413 | 0 | struct pl_dir_entry *a_entry = (struct pl_dir_entry*) a; |
414 | 0 | struct pl_dir_entry *b_entry = (struct pl_dir_entry*) b; |
415 | 0 | if (a_entry->is_dir == b_entry->is_dir) { |
416 | 0 | return mp_natural_sort_cmp(a_entry->name, b_entry->name); |
417 | 0 | } else { |
418 | 0 | return a_entry->is_dir ? 1 : -1; |
419 | 0 | } |
420 | 0 | } |
421 | | |
422 | | static bool test_path(struct pl_parser *p, char *path, int autocreate) |
423 | 0 | { |
424 | 0 | if (autocreate & AUTO_ANY) |
425 | 0 | return true; |
426 | | |
427 | 0 | if (!strcmp(path, p->real_stream->path)) |
428 | 0 | return true; |
429 | | |
430 | 0 | bstr ext = mp_get_ext(bstr0(path)); |
431 | 0 | if (autocreate & AUTO_VIDEO && bstr_in_list0(ext, p->mp_opts->video_exts)) |
432 | 0 | return true; |
433 | 0 | if (autocreate & AUTO_AUDIO && bstr_in_list0(ext, p->mp_opts->audio_exts)) |
434 | 0 | return true; |
435 | 0 | if (autocreate & AUTO_IMAGE && bstr_in_list0(ext, p->mp_opts->image_exts)) |
436 | 0 | return true; |
437 | 0 | if (autocreate & AUTO_ARCHIVE && bstr_in_list0(ext, p->mp_opts->archive_exts)) |
438 | 0 | return true; |
439 | 0 | if (autocreate & AUTO_PLAYLIST && bstr_in_list0(ext, p->mp_opts->playlist_exts)) |
440 | 0 | return true; |
441 | | |
442 | 0 | return false; |
443 | 0 | } |
444 | | |
445 | | // Return true if this was a readable directory. |
446 | | static bool scan_dir(struct pl_parser *p, char *path, |
447 | | struct stat *dir_stack, int num_dir_stack, int autocreate) |
448 | 0 | { |
449 | 0 | if (strlen(path) >= 8192 || num_dir_stack == MAX_DIR_STACK) |
450 | 0 | return false; // things like mount bind loops |
451 | | |
452 | 0 | DIR *dp = opendir(path); |
453 | 0 | if (!dp) { |
454 | 0 | MP_ERR(p, "Could not read directory.\n"); |
455 | 0 | return false; |
456 | 0 | } |
457 | | |
458 | 0 | struct pl_dir_entry *dir_entries = NULL; |
459 | 0 | int num_dir_entries = 0; |
460 | 0 | int path_len = strlen(path); |
461 | 0 | int dir_mode = p->opts->dir_mode; |
462 | |
|
463 | 0 | struct dirent *ep; |
464 | 0 | while ((ep = readdir(dp))) { |
465 | 0 | if (ep->d_name[0] == '.') |
466 | 0 | continue; |
467 | | |
468 | 0 | if (mp_cancel_test(p->s->cancel)) |
469 | 0 | break; |
470 | | |
471 | 0 | char *file = mp_path_join(p, path, ep->d_name); |
472 | |
|
473 | 0 | struct stat st; |
474 | 0 | if (stat(file, &st) == 0 && S_ISDIR(st.st_mode)) { |
475 | 0 | if (dir_mode != DIR_IGNORE) { |
476 | 0 | for (int n = 0; n < num_dir_stack; n++) { |
477 | 0 | if (same_st(&dir_stack[n], &st)) { |
478 | 0 | MP_VERBOSE(p, "Skip recursive entry: %s\n", file); |
479 | 0 | goto skip; |
480 | 0 | } |
481 | 0 | } |
482 | | |
483 | 0 | struct pl_dir_entry d = {file, &file[path_len], st, true}; |
484 | 0 | MP_TARRAY_APPEND(p, dir_entries, num_dir_entries, d); |
485 | 0 | } |
486 | 0 | } else { |
487 | 0 | struct pl_dir_entry f = {file, &file[path_len], .is_dir = false}; |
488 | 0 | MP_TARRAY_APPEND(p, dir_entries, num_dir_entries, f); |
489 | 0 | } |
490 | | |
491 | 0 | skip: ; |
492 | 0 | } |
493 | 0 | closedir(dp); |
494 | |
|
495 | 0 | if (dir_entries) |
496 | 0 | qsort(dir_entries, num_dir_entries, sizeof(dir_entries[0]), cmp_dir_entry); |
497 | |
|
498 | 0 | for (int n = 0; n < num_dir_entries; n++) { |
499 | 0 | char *file = dir_entries[n].path; |
500 | 0 | if (dir_mode == DIR_RECURSIVE && dir_entries[n].is_dir) { |
501 | 0 | dir_stack[num_dir_stack] = dir_entries[n].st; |
502 | 0 | scan_dir(p, file, dir_stack, num_dir_stack + 1, autocreate); |
503 | 0 | } |
504 | 0 | else { |
505 | 0 | if (dir_entries[n].is_dir || test_path(p, file, autocreate)) |
506 | 0 | playlist_append_file(p->pl, dir_entries[n].path); |
507 | 0 | } |
508 | 0 | } |
509 | |
|
510 | 0 | return true; |
511 | 0 | } |
512 | | |
513 | | static enum autocreate_mode get_directory_filter(struct pl_parser *p) |
514 | 183k | { |
515 | 183k | enum autocreate_mode autocreate = AUTO_NONE; |
516 | 183k | if (!p->directory_filter || !p->directory_filter[0]) |
517 | 0 | autocreate = AUTO_ANY; |
518 | 183k | if (bstr_in_list0(bstr0("video"), p->directory_filter)) |
519 | 183k | autocreate |= AUTO_VIDEO; |
520 | 183k | if (bstr_in_list0(bstr0("audio"), p->directory_filter)) |
521 | 183k | autocreate |= AUTO_AUDIO; |
522 | 183k | if (bstr_in_list0(bstr0("image"), p->directory_filter)) |
523 | 183k | autocreate |= AUTO_IMAGE; |
524 | 183k | if (bstr_in_list0(bstr0("archive"), p->directory_filter)) |
525 | 183k | autocreate |= AUTO_ARCHIVE; |
526 | 183k | if (bstr_in_list0(bstr0("playlist"), p->directory_filter)) |
527 | 183k | autocreate |= AUTO_PLAYLIST; |
528 | 183k | return autocreate; |
529 | 183k | } |
530 | | |
531 | | static int parse_dir(struct pl_parser *p) |
532 | 183k | { |
533 | 183k | int ret = -1; |
534 | 183k | struct stream *stream = p->real_stream; |
535 | 183k | enum autocreate_mode autocreate = AUTO_NONE; |
536 | 183k | p->pl->playlist_dir = NULL; |
537 | 183k | if (p->autocreate_playlist && p->real_stream->is_local_fs && p->real_stream->is_regular && |
538 | 0 | !p->real_stream->is_directory) { |
539 | 0 | bstr ext = mp_get_ext(bstr0(p->real_stream->url)); |
540 | 0 | switch (p->autocreate_playlist) { |
541 | 0 | case 1: // filter |
542 | 0 | autocreate = get_directory_filter(p); |
543 | 0 | break; |
544 | 0 | case 2: // same |
545 | 0 | if (bstr_in_list0(ext, p->mp_opts->video_exts)) { |
546 | 0 | autocreate = AUTO_VIDEO; |
547 | 0 | } else if (bstr_in_list0(ext, p->mp_opts->audio_exts)) { |
548 | 0 | autocreate = AUTO_AUDIO; |
549 | 0 | } else if (bstr_in_list0(ext, p->mp_opts->image_exts)) { |
550 | 0 | autocreate = AUTO_IMAGE; |
551 | 0 | } else if (bstr_in_list0(ext, p->mp_opts->archive_exts)) { |
552 | 0 | autocreate = AUTO_ARCHIVE; |
553 | 0 | } else if (bstr_in_list0(ext, p->mp_opts->playlist_exts)) { |
554 | 0 | autocreate = AUTO_PLAYLIST; |
555 | 0 | } |
556 | 0 | break; |
557 | 0 | } |
558 | 0 | int flags = STREAM_READ_FILE_FLAGS_DEFAULT; |
559 | 0 | bstr dir = mp_dirname(p->real_stream->url); |
560 | 0 | if (!dir.len) |
561 | 0 | autocreate = AUTO_NONE; |
562 | 0 | if (autocreate != AUTO_NONE) { |
563 | 0 | stream = stream_create(bstrdup0(p, dir), flags, NULL, p->global); |
564 | 0 | p->pl->playlist_dir = bstrdup0(p->pl, dir); |
565 | 0 | } |
566 | 183k | } else { |
567 | 183k | autocreate = get_directory_filter(p); |
568 | 183k | } |
569 | 183k | if (!stream->is_directory) |
570 | 183k | goto done; |
571 | 0 | if (p->probing) { |
572 | 0 | ret = 0; |
573 | 0 | goto done; |
574 | 0 | } |
575 | | |
576 | 0 | char *path = mp_file_get_path(p, bstr0(stream->url)); |
577 | 0 | if (!path) |
578 | 0 | goto done; |
579 | | |
580 | 0 | if (autocreate == AUTO_NONE) |
581 | 0 | goto done; |
582 | | |
583 | 0 | struct stat dir_stack[MAX_DIR_STACK]; |
584 | |
|
585 | 0 | if (p->opts->dir_mode == DIR_AUTO) |
586 | 0 | p->opts->dir_mode = p->mp_opts->shuffle ? DIR_RECURSIVE : DIR_LAZY; |
587 | |
|
588 | 0 | scan_dir(p, path, dir_stack, 0, autocreate); |
589 | |
|
590 | 0 | ret = p->pl->num_entries > 0 ? 0 : -1; |
591 | |
|
592 | 183k | done: |
593 | 183k | if (stream != p->real_stream) |
594 | 0 | free_stream(stream); |
595 | 183k | return ret; |
596 | 0 | } |
597 | | |
598 | | #define MIME_TYPES(...) \ |
599 | | .mime_types = (const char*const[]){__VA_ARGS__, NULL} |
600 | | |
601 | | struct pl_format { |
602 | | const char *name; |
603 | | int (*parse)(struct pl_parser *p); |
604 | | const char *const *mime_types; |
605 | | }; |
606 | | |
607 | | static const struct pl_format dir_formats[] = { |
608 | | {"directory", parse_dir}, |
609 | | {0}, |
610 | | }; |
611 | | |
612 | | static const struct pl_format playlist_formats[] = { |
613 | | {"m3u", parse_m3u, |
614 | | MIME_TYPES("audio/mpegurl", "audio/x-mpegurl", "application/x-mpegurl")}, |
615 | | {"strm", parse_m3u}, |
616 | | {"ini", parse_ref_init}, |
617 | | {"pls", parse_pls, |
618 | | MIME_TYPES("audio/x-scpls")}, |
619 | | {"url", parse_url}, |
620 | | {"txt", parse_txt}, |
621 | | {0}, |
622 | | }; |
623 | | |
624 | | static const struct pl_format *probe_pl(struct pl_parser *p, const struct pl_format *fmts) |
625 | 260k | { |
626 | 260k | int64_t start = stream_tell(p->s); |
627 | 260k | const struct pl_format *fmt = fmts; |
628 | 877k | while (fmt->name) { |
629 | 621k | stream_seek(p->s, start); |
630 | 621k | if (check_mimetype(p->s, fmt->mime_types)) { |
631 | 0 | MP_VERBOSE(p, "forcing format by mime-type.\n"); |
632 | 0 | p->force = true; |
633 | 0 | return fmt; |
634 | 0 | } |
635 | 621k | if (fmt->parse(p) >= 0) |
636 | 5.00k | return fmt; |
637 | 616k | fmt++; |
638 | 616k | } |
639 | 255k | return NULL; |
640 | 260k | } |
641 | | |
642 | | extern const demuxer_desc_t demuxer_desc_playlist; |
643 | | extern const demuxer_desc_t demuxer_desc_directory; |
644 | | |
645 | | static int open_file(struct demuxer *demuxer, enum demux_check check) |
646 | 260k | { |
647 | 260k | if (!demuxer->access_references) |
648 | 0 | return -1; |
649 | | |
650 | 260k | bool force = check < DEMUX_CHECK_UNSAFE || check == DEMUX_CHECK_REQUEST; |
651 | | |
652 | 260k | struct pl_parser *p = talloc_zero(NULL, struct pl_parser); |
653 | 260k | p->global = demuxer->global; |
654 | 260k | p->log = demuxer->log; |
655 | 260k | p->pl = talloc_zero(p, struct playlist); |
656 | 260k | p->real_stream = demuxer->stream; |
657 | | |
658 | 260k | struct demux_opts *opts = mp_get_config_group(p, p->global, &demux_conf); |
659 | 260k | p->codepage = opts->meta_cp; |
660 | | |
661 | 260k | char probe[PROBE_SIZE]; |
662 | 260k | int probe_len = stream_read_peek(p->real_stream, probe, sizeof(probe)); |
663 | 260k | p->s = stream_memory_open(demuxer->global, probe, probe_len); |
664 | 260k | p->s->mime_type = demuxer->stream->mime_type; |
665 | 260k | p->utf16 = stream_skip_bom(p->s); |
666 | 260k | p->force = force; |
667 | 260k | p->check_level = check; |
668 | 260k | p->probing = true; |
669 | 260k | p->autocreate_playlist = demuxer->params->allow_playlist_create ? opts->autocreate_playlist : 0; |
670 | 260k | p->directory_filter = opts->directory_filter; |
671 | 260k | p->mp_opts = mp_get_config_group(demuxer, demuxer->global, &mp_opt_root); |
672 | 260k | p->opts = mp_get_config_group(demuxer, demuxer->global, &demux_playlist_conf); |
673 | | |
674 | 260k | const struct pl_format *fmts = playlist_formats; |
675 | 260k | if (demuxer->desc == &demuxer_desc_directory) |
676 | 183k | fmts = dir_formats; |
677 | | |
678 | 260k | const struct pl_format *fmt = probe_pl(p, fmts); |
679 | 260k | free_stream(p->s); |
680 | 260k | playlist_clear(p->pl); |
681 | 260k | if (!fmt) { |
682 | 255k | talloc_free(p); |
683 | 255k | return -1; |
684 | 255k | } |
685 | | |
686 | 5.00k | p->probing = false; |
687 | 5.00k | p->error = false; |
688 | 5.00k | p->s = demuxer->stream; |
689 | 5.00k | p->utf16 = stream_skip_bom(p->s); |
690 | 5.00k | bool ok = fmt->parse(p) >= 0 && !p->error; |
691 | 5.00k | playlist_set_stream_flags(p->pl, demuxer->stream_origin); |
692 | 5.00k | demuxer->playlist = talloc_steal(demuxer, p->pl); |
693 | 5.00k | demuxer->filetype = p->format ? p->format : fmt->name; |
694 | 5.00k | demuxer->fully_read = true; |
695 | 5.00k | talloc_free(p); |
696 | 5.00k | if (ok) |
697 | 5.00k | demux_close_stream(demuxer); |
698 | 5.00k | return ok ? 0 : -1; |
699 | 260k | } |
700 | | |
701 | | const demuxer_desc_t demuxer_desc_directory = { |
702 | | .name = "directory", |
703 | | .desc = "Playlist dir", |
704 | | .open = open_file, |
705 | | }; |
706 | | |
707 | | const demuxer_desc_t demuxer_desc_playlist = { |
708 | | .name = "playlist", |
709 | | .desc = "Playlist file", |
710 | | .open = open_file, |
711 | | }; |