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 <assert.h>
19 #include "config.h"
20 #include "playlist.h"
21 #include "common/common.h"
22 #include "common/global.h"
23 #include "common/msg.h"
24 #include "mpv_talloc.h"
25 #include "options/path.h"
26 
27 #include "demux/demux.h"
28 #include "stream/stream.h"
29 
playlist_entry_new(const char * filename)30 struct playlist_entry *playlist_entry_new(const char *filename)
31 {
32     struct playlist_entry *e = talloc_zero(NULL, struct playlist_entry);
33     char *local_filename = mp_file_url_to_filename(e, bstr0(filename));
34     e->filename = local_filename ? local_filename : talloc_strdup(e, filename);
35     e->stream_flags = STREAM_ORIGIN_DIRECT;
36     e->original_index = -1;
37     return e;
38 }
39 
playlist_entry_add_param(struct playlist_entry * e,bstr name,bstr value)40 void playlist_entry_add_param(struct playlist_entry *e, bstr name, bstr value)
41 {
42     struct playlist_param p = {bstrdup(e, name), bstrdup(e, value)};
43     MP_TARRAY_APPEND(e, e->params, e->num_params, p);
44 }
45 
playlist_entry_add_params(struct playlist_entry * e,struct playlist_param * params,int num_params)46 void playlist_entry_add_params(struct playlist_entry *e,
47                                struct playlist_param *params,
48                                int num_params)
49 {
50     for (int n = 0; n < num_params; n++)
51         playlist_entry_add_param(e, params[n].name, params[n].value);
52 }
53 
playlist_update_indexes(struct playlist * pl,int start,int end)54 static void playlist_update_indexes(struct playlist *pl, int start, int end)
55 {
56     start = MPMAX(start, 0);
57     end = end < 0 ? pl->num_entries : MPMIN(end, pl->num_entries);
58 
59     for (int n = start; n < end; n++)
60         pl->entries[n]->pl_index = n;
61 }
62 
playlist_add(struct playlist * pl,struct playlist_entry * add)63 void playlist_add(struct playlist *pl, struct playlist_entry *add)
64 {
65     assert(add->filename);
66     MP_TARRAY_APPEND(pl, pl->entries, pl->num_entries, add);
67     add->pl = pl;
68     add->pl_index = pl->num_entries - 1;
69     add->id = ++pl->id_alloc;
70     talloc_steal(pl, add);
71 }
72 
playlist_entry_unref(struct playlist_entry * e)73 void playlist_entry_unref(struct playlist_entry *e)
74 {
75     e->reserved--;
76     if (e->reserved < 0) {
77         assert(!e->pl);
78         talloc_free(e);
79     }
80 }
81 
playlist_remove(struct playlist * pl,struct playlist_entry * entry)82 void playlist_remove(struct playlist *pl, struct playlist_entry *entry)
83 {
84     assert(pl && entry->pl == pl);
85 
86     if (pl->current == entry) {
87         pl->current = playlist_entry_get_rel(entry, 1);
88         pl->current_was_replaced = true;
89     }
90 
91     MP_TARRAY_REMOVE_AT(pl->entries, pl->num_entries, entry->pl_index);
92     playlist_update_indexes(pl, entry->pl_index, -1);
93 
94     entry->pl = NULL;
95     entry->pl_index = -1;
96     ta_set_parent(entry, NULL);
97 
98     entry->removed = true;
99     playlist_entry_unref(entry);
100 }
101 
playlist_clear(struct playlist * pl)102 void playlist_clear(struct playlist *pl)
103 {
104     for (int n = pl->num_entries - 1; n >= 0; n--)
105         playlist_remove(pl, pl->entries[n]);
106     assert(!pl->current);
107     pl->current_was_replaced = false;
108 }
109 
playlist_clear_except_current(struct playlist * pl)110 void playlist_clear_except_current(struct playlist *pl)
111 {
112     for (int n = pl->num_entries - 1; n >= 0; n--) {
113         if (pl->entries[n] != pl->current)
114             playlist_remove(pl, pl->entries[n]);
115     }
116 }
117 
118 // Moves the entry so that it takes "at"'s place (or move to end, if at==NULL).
playlist_move(struct playlist * pl,struct playlist_entry * entry,struct playlist_entry * at)119 void playlist_move(struct playlist *pl, struct playlist_entry *entry,
120                    struct playlist_entry *at)
121 {
122     if (entry == at)
123         return;
124 
125     assert(entry && entry->pl == pl);
126     assert(!at || at->pl == pl);
127 
128     int index = at ? at->pl_index : pl->num_entries;
129     MP_TARRAY_INSERT_AT(pl, pl->entries, pl->num_entries, index, entry);
130 
131     int old_index = entry->pl_index;
132     if (old_index >= index)
133         old_index += 1;
134     MP_TARRAY_REMOVE_AT(pl->entries, pl->num_entries, old_index);
135 
136     playlist_update_indexes(pl, MPMIN(index - 1, old_index - 1),
137                                 MPMAX(index + 1, old_index + 1));
138 }
139 
playlist_add_file(struct playlist * pl,const char * filename)140 void playlist_add_file(struct playlist *pl, const char *filename)
141 {
142     playlist_add(pl, playlist_entry_new(filename));
143 }
144 
playlist_shuffle(struct playlist * pl)145 void playlist_shuffle(struct playlist *pl)
146 {
147     for (int n = 0; n < pl->num_entries; n++)
148         pl->entries[n]->original_index = n;
149     for (int n = 0; n < pl->num_entries - 1; n++) {
150         int j = (int)((double)(pl->num_entries - n) * rand() / (RAND_MAX + 1.0));
151         MPSWAP(struct playlist_entry *, pl->entries[n], pl->entries[n + j]);
152     }
153     playlist_update_indexes(pl, 0, -1);
154 }
155 
156 #define CMP_INT(a, b) ((a) == (b) ? 0 : ((a) > (b) ? 1 : -1))
157 
cmp_unshuffle(const void * a,const void * b)158 static int cmp_unshuffle(const void *a, const void *b)
159 {
160     struct playlist_entry *ea = *(struct playlist_entry **)a;
161     struct playlist_entry *eb = *(struct playlist_entry **)b;
162 
163     if (ea->original_index >= 0 && ea->original_index != eb->original_index)
164         return CMP_INT(ea->original_index, eb->original_index);
165     return CMP_INT(ea->pl_index, eb->pl_index);
166 }
167 
playlist_unshuffle(struct playlist * pl)168 void playlist_unshuffle(struct playlist *pl)
169 {
170     if (pl->num_entries)
171         qsort(pl->entries, pl->num_entries, sizeof(pl->entries[0]), cmp_unshuffle);
172     playlist_update_indexes(pl, 0, -1);
173 }
174 
175 // (Explicitly ignores current_was_replaced.)
playlist_get_first(struct playlist * pl)176 struct playlist_entry *playlist_get_first(struct playlist *pl)
177 {
178     return pl->num_entries ? pl->entries[0] : NULL;
179 }
180 
181 // (Explicitly ignores current_was_replaced.)
playlist_get_last(struct playlist * pl)182 struct playlist_entry *playlist_get_last(struct playlist *pl)
183 {
184     return pl->num_entries ? pl->entries[pl->num_entries - 1] : NULL;
185 }
186 
playlist_get_next(struct playlist * pl,int direction)187 struct playlist_entry *playlist_get_next(struct playlist *pl, int direction)
188 {
189     assert(direction == -1 || direction == +1);
190     if (!pl->current)
191         return NULL;
192     assert(pl->current->pl == pl);
193     if (direction < 0)
194         return playlist_entry_get_rel(pl->current, -1);
195     return pl->current_was_replaced ? pl->current :
196            playlist_entry_get_rel(pl->current, 1);
197 }
198 
199 // (Explicitly ignores current_was_replaced.)
playlist_entry_get_rel(struct playlist_entry * e,int direction)200 struct playlist_entry *playlist_entry_get_rel(struct playlist_entry *e,
201                                               int direction)
202 {
203     assert(direction == -1 || direction == +1);
204     if (!e->pl)
205         return NULL;
206     return playlist_entry_from_index(e->pl, e->pl_index + direction);
207 }
208 
playlist_add_base_path(struct playlist * pl,bstr base_path)209 void playlist_add_base_path(struct playlist *pl, bstr base_path)
210 {
211     if (base_path.len == 0 || bstrcmp0(base_path, ".") == 0)
212         return;
213     for (int n = 0; n < pl->num_entries; n++) {
214         struct playlist_entry *e = pl->entries[n];
215         if (!mp_is_url(bstr0(e->filename))) {
216             char *new_file = mp_path_join_bstr(e, base_path, bstr0(e->filename));
217             talloc_free(e->filename);
218             e->filename = new_file;
219         }
220     }
221 }
222 
223 // Add redirected_from as new redirect entry to each item in pl.
playlist_add_redirect(struct playlist * pl,const char * redirected_from)224 void playlist_add_redirect(struct playlist *pl, const char *redirected_from)
225 {
226     for (int n = 0; n < pl->num_entries; n++) {
227         struct playlist_entry *e = pl->entries[n];
228         if (e->num_redirects >= 10) // arbitrary limit for sanity
229             continue;
230         char *s = talloc_strdup(e, redirected_from);
231         if (s)
232             MP_TARRAY_APPEND(e, e->redirects, e->num_redirects, s);
233     }
234 }
235 
playlist_set_stream_flags(struct playlist * pl,int flags)236 void playlist_set_stream_flags(struct playlist *pl, int flags)
237 {
238     for (int n = 0; n < pl->num_entries; n++)
239         pl->entries[n]->stream_flags = flags;
240 }
241 
playlist_transfer_entries_to(struct playlist * pl,int dst_index,struct playlist * source_pl)242 static int64_t playlist_transfer_entries_to(struct playlist *pl, int dst_index,
243                                             struct playlist *source_pl)
244 {
245     assert(pl != source_pl);
246     struct playlist_entry *first = playlist_get_first(source_pl);
247 
248     int count = source_pl->num_entries;
249     MP_TARRAY_INSERT_N_AT(pl, pl->entries, pl->num_entries, dst_index, count);
250 
251     for (int n = 0; n < count; n++) {
252         struct playlist_entry *e = source_pl->entries[n];
253         e->pl = pl;
254         e->pl_index = dst_index + n;
255         e->id = ++pl->id_alloc;
256         pl->entries[e->pl_index] = e;
257         talloc_steal(pl, e);
258     }
259 
260     playlist_update_indexes(pl, dst_index + count, -1);
261     source_pl->num_entries = 0;
262 
263     return first ? first->id : 0;
264 }
265 
266 // Move all entries from source_pl to pl, appending them after the current entry
267 // of pl. source_pl will be empty, and all entries have changed ownership to pl.
268 // Return the new ID of the first added entry within pl (0 if source_pl was
269 // empty). The IDs of all added entries increase by 1 each entry (you can
270 // predict the ID of the last entry).
playlist_transfer_entries(struct playlist * pl,struct playlist * source_pl)271 int64_t playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl)
272 {
273 
274     int add_at = pl->num_entries;
275     if (pl->current) {
276         add_at = pl->current->pl_index + 1;
277         if (pl->current_was_replaced)
278             add_at += 1;
279     }
280     assert(add_at >= 0);
281     assert(add_at <= pl->num_entries);
282 
283     return playlist_transfer_entries_to(pl, add_at, source_pl);
284 }
285 
playlist_append_entries(struct playlist * pl,struct playlist * source_pl)286 int64_t playlist_append_entries(struct playlist *pl, struct playlist *source_pl)
287 {
288     return playlist_transfer_entries_to(pl, pl->num_entries, source_pl);
289 }
290 
291 // Return number of entries between list start and e.
292 // Return -1 if e is not on the list, or if e is NULL.
playlist_entry_to_index(struct playlist * pl,struct playlist_entry * e)293 int playlist_entry_to_index(struct playlist *pl, struct playlist_entry *e)
294 {
295     if (!e || e->pl != pl)
296         return -1;
297     return e->pl_index;
298 }
299 
playlist_entry_count(struct playlist * pl)300 int playlist_entry_count(struct playlist *pl)
301 {
302     return pl->num_entries;
303 }
304 
305 // Return entry for which playlist_entry_to_index() would return index.
306 // Return NULL if not found.
playlist_entry_from_index(struct playlist * pl,int index)307 struct playlist_entry *playlist_entry_from_index(struct playlist *pl, int index)
308 {
309     return index >= 0 && index < pl->num_entries ? pl->entries[index] : NULL;
310 }
311 
playlist_parse_file(const char * file,struct mp_cancel * cancel,struct mpv_global * global)312 struct playlist *playlist_parse_file(const char *file, struct mp_cancel *cancel,
313                                      struct mpv_global *global)
314 {
315     struct mp_log *log = mp_log_new(NULL, global->log, "!playlist_parser");
316     mp_verbose(log, "Parsing playlist file %s...\n", file);
317 
318     struct demuxer_params p = {
319         .force_format = "playlist",
320         .stream_flags = STREAM_ORIGIN_DIRECT,
321     };
322     struct demuxer *d = demux_open_url(file, &p, cancel, global);
323     if (!d) {
324         talloc_free(log);
325         return NULL;
326     }
327 
328     struct playlist *ret = NULL;
329     if (d && d->playlist) {
330         ret = talloc_zero(NULL, struct playlist);
331         playlist_transfer_entries(ret, d->playlist);
332         if (d->filetype && strcmp(d->filetype, "hls") == 0) {
333             mp_warn(log, "This might be a HLS stream. For correct operation, "
334                          "pass it to the player\ndirectly. Don't use --playlist.\n");
335         }
336     }
337     demux_free(d);
338 
339     if (ret) {
340         mp_verbose(log, "Playlist successfully parsed\n");
341     } else {
342         mp_err(log, "Error while parsing playlist\n");
343     }
344 
345     if (ret && !ret->num_entries)
346         mp_warn(log, "Warning: empty playlist\n");
347 
348     talloc_free(log);
349     return ret;
350 }
351