1 /*
2  *			GPAC - Multimedia Framework C SDK
3  *
4  *			Authors: Jean Le Feuvre
5  *			Copyright (c) Telecom ParisTech 2018-2019
6  *					All rights reserved
7  *
8  *  This file is part of GPAC / file concatenator filter
9  *
10  *  GPAC is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU Lesser General Public License as published by
12  *  the Free Software Foundation; either version 2, or (at your option)
13  *  any later version.
14  *
15  *  GPAC is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU Lesser General Public License for more details.
19  *
20  *  You should have received a copy of the GNU Lesser General Public
21  *  License along with this library; see the file COPYING.  If not, write to
22  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23  *
24  */
25 
26 #include <gpac/filters.h>
27 #include <gpac/constants.h>
28 #include <gpac/thread.h>
29 #include <gpac/list.h>
30 #include <gpac/bitstream.h>
31 
32 #ifndef GPAC_DISABLE_AV_PARSERS
33 #include <gpac/avparse.h>
34 #endif
35 
36 typedef struct
37 {
38 	GF_FilterPid *ipid;
39 	GF_FilterPid *opid;
40 	u32 stream_type;
41 	//in current timescale
42 	u64 max_cts, max_dts;
43 	u32 o_timescale, timescale;
44 	//in original timescale
45 	u64 cts_o, dts_o;
46 	Bool single_frame;
47 	Bool is_eos;
48 	u64 dts_sub;
49 	u64 first_dts_plus_one;
50 
51 	Bool is_playing;
52 } FileListPid;
53 
54 typedef struct
55 {
56 	char *file_name;
57 	u64 last_mod_time;
58 	u64 file_size;
59 } FileListEntry;
60 
61 enum
62 {
63 	FL_SORT_NONE=0,
64 	FL_SORT_NAME,
65 	FL_SORT_SIZE,
66 	FL_SORT_DATE,
67 	FL_SORT_DATEX,
68 };
69 
70 typedef struct
71 {
72 	//opts
73 	Bool revert;
74 	s32 floop;
75 	u32 fsort;
76 	GF_List *srcs;
77 	GF_Fraction fdur;
78 	u32 timescale;
79 
80 	GF_FilterPid *file_pid;
81 	char *file_path;
82 	u32 last_url_crc;
83 	u32 last_url_lineno;
84 	Bool load_next;
85 
86 	GF_List *filter_srcs;
87 	GF_List *io_pids;
88 	Bool is_eos;
89 
90 	//in 1000000 Hz
91 	u64 cts_offset, dts_offset, dts_sub_plus_one;
92 
93 	u32 nb_repeat;
94 	Double start, stop;
95 
96 	GF_List *file_list;
97 	s32 file_list_idx;
98 
99 	u64 current_file_dur;
100 
101 	char szCom[GF_MAX_PATH];
102 } GF_FileListCtx;
103 
104 static const GF_FilterCapability FileListCapsSrc[] =
105 {
106 	CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
107 	CAP_UINT(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_CODECID, GF_CODECID_NONE),
108 };
109 
filelist_start_ipid(GF_FileListCtx * ctx,FileListPid * iopid)110 static void filelist_start_ipid(GF_FileListCtx *ctx, FileListPid *iopid)
111 {
112 	GF_FilterEvent evt;
113 	iopid->is_eos = GF_FALSE;
114 
115 	//if we reattached the input, we must send a play request
116 	gf_filter_pid_init_play_event(iopid->ipid, &evt, ctx->start, 1.0, "FileList");
117 	gf_filter_pid_send_event(iopid->ipid, &evt);
118 
119 	//and convert back cts/dts offsets from 1Mhs to OLD timescale (since we dispatch in this timescale)
120 	iopid->dts_o = ctx->dts_offset;
121 	iopid->dts_o *= iopid->o_timescale;
122 	iopid->dts_o /= 1000000;
123 
124 	iopid->cts_o = ctx->cts_offset;
125 	iopid->cts_o *= iopid->o_timescale;
126 	iopid->cts_o /= 1000000;
127 	iopid->max_cts = iopid->max_dts = 0;
128 
129 	iopid->first_dts_plus_one = 0;
130 }
131 
filelist_configure_pid(GF_Filter * filter,GF_FilterPid * pid,Bool is_remove)132 GF_Err filelist_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
133 {
134 	FileListPid *iopid;
135 	const GF_PropertyValue *p;
136 	u32 i, count;
137 	Bool reassign = GF_FALSE;
138 	GF_FileListCtx *ctx = gf_filter_get_udta(filter);
139 
140 	if (is_remove) {
141 		if (pid==ctx->file_pid)
142 			ctx->file_pid = NULL;
143 		else {
144 			iopid = gf_filter_pid_get_udta(pid);
145 			if (iopid) iopid->ipid = NULL;
146 		}
147 		return GF_OK;
148 	}
149 
150 	if (!ctx->file_pid && !ctx->file_list) {
151 		if (! gf_filter_pid_check_caps(pid))
152 			return GF_NOT_SUPPORTED;
153 		ctx->file_pid = pid;
154 		//we want the complete file
155 		gf_filter_pid_set_framing_mode(pid, GF_TRUE);
156 
157 		//we will declare pids later
158 
159 		//from now on we only accept the above caps
160 		gf_filter_override_caps(filter, FileListCapsSrc, 2);
161 	}
162 	if (ctx->file_pid == pid) return GF_OK;
163 
164 	iopid = NULL;
165 	count = gf_list_count(ctx->io_pids);
166 	for (i=0; i<count; i++) {
167 		iopid = gf_list_get(ctx->io_pids, i);
168 		if (iopid->ipid==pid) break;
169 		//check matching stream types if out pit not connected, and reuse if matching
170 		if (!iopid->ipid) {
171 			p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE);
172 			assert(p);
173 			if (p->value.uint == iopid->stream_type) {
174 				iopid->ipid = pid;
175 				iopid->timescale = gf_filter_pid_get_timescale(pid);
176 				reassign = GF_TRUE;
177 				break;
178 			}
179 		}
180 		iopid = NULL;
181 	}
182 
183 	if (!iopid) {
184 		GF_SAFEALLOC(iopid, FileListPid);
185 		if (!iopid) return GF_OUT_OF_MEM;
186 		iopid->ipid = pid;
187 		if (ctx->timescale) iopid->o_timescale = ctx->timescale;
188 		else {
189 			iopid->o_timescale = gf_filter_pid_get_timescale(pid);
190 			if (!iopid->o_timescale) iopid->o_timescale = 1000;
191 		}
192 		gf_list_add(ctx->io_pids, iopid);
193 	}
194 	gf_filter_pid_set_udta(pid, iopid);
195 
196 	if (!iopid->opid) {
197 		iopid->opid = gf_filter_pid_new(filter);
198 		p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE);
199 		assert(p);
200 		iopid->stream_type = p->value.uint;
201 	}
202 
203 	gf_filter_pid_reset_properties(iopid->opid);
204 	//copy properties at init or reconfig
205 	gf_filter_pid_copy_properties(iopid->opid, iopid->ipid);
206 	//we could further optimize by querying the duration of all sources in the list
207 	gf_filter_pid_set_property(iopid->opid, GF_PROP_PID_PLAYBACK_MODE, &PROP_UINT(GF_PLAYBACK_MODE_NONE) );
208 	gf_filter_pid_set_property(iopid->opid, GF_PROP_PID_TIMESCALE, &PROP_UINT(iopid->o_timescale) );
209 	gf_filter_pid_set_property(iopid->opid, GF_PROP_PID_NB_FRAMES, NULL );
210 
211 	p = gf_filter_pid_get_property(pid, GF_PROP_PID_NB_FRAMES);
212 	iopid->single_frame = (p && (p->value.uint==1)) ? GF_TRUE : GF_FALSE ;
213 	iopid->timescale = gf_filter_pid_get_timescale(pid);
214 	if (!iopid->timescale) iopid->timescale = 1000;
215 
216 #if 0
217 	p = gf_filter_pid_get_property(pid, GF_PROP_PID_DELAY);
218 	if (p) {
219 		s64 delay = p->value.sint;
220 		delay *= iopid->timescale;
221 		delay /= iopid->o_timescale;
222 	}
223 #endif
224 
225 	//if we reattached the input, we must send a play request
226 	if (reassign) {
227 		filelist_start_ipid(ctx, iopid);
228 	}
229 	p = gf_filter_pid_get_property(pid, GF_PROP_PID_URL);
230 	if (p)
231 	 	gf_filter_pid_set_property(iopid->opid, GF_PROP_PID_URL, p);
232 
233 	return GF_OK;
234 }
235 
filelist_process_event(GF_Filter * filter,const GF_FilterEvent * evt)236 static Bool filelist_process_event(GF_Filter *filter, const GF_FilterEvent *evt)
237 {
238 	u32 i, count;
239 	FileListPid *iopid;
240 	GF_FilterEvent fevt;
241 	GF_FileListCtx *ctx = gf_filter_get_udta(filter);
242 
243 	//manually forward event to all input, except our file in pid
244 	memcpy(&fevt, evt, sizeof(GF_FilterEvent));
245 	count = gf_list_count(ctx->io_pids);
246 	for (i=0; i<count; i++) {
247 		iopid = gf_list_get(ctx->io_pids, i);
248 		if (!iopid->ipid) continue;
249 
250 		fevt.base.on_pid = iopid->ipid;
251 		if (evt->base.type==GF_FEVT_PLAY) {
252 			gf_filter_pid_init_play_event(iopid->ipid, &fevt, ctx->start, 1.0, "FileList");
253 			iopid->is_playing = GF_TRUE;
254 			iopid->is_eos = GF_FALSE;
255 		} else if (evt->base.type==GF_FEVT_STOP) {
256 			iopid->is_playing = GF_FALSE;
257 			iopid->is_eos = GF_TRUE;
258 		}
259 		gf_filter_pid_send_event(iopid->ipid, &fevt);
260 	}
261 	//and cancel
262 	return GF_TRUE;
263 }
264 
filelist_parse_arg(char * com,char * name,Bool is_float,u32 * int_val,Double * float_val)265 void filelist_parse_arg(char *com, char *name, Bool is_float, u32 *int_val, Double *float_val)
266 {
267 	char *val = strstr(com, name);
268 	if (val) {
269 		char c= 0;
270 		char *sep = strchr(val, ' ');
271 		if (!sep) sep = strchr(val, ',');
272 		if (!sep) sep = strchr(val, '\n');
273 		if (!sep) sep = strchr(val, '\r');
274 		if (sep) {
275 			c = sep[0];
276 			sep[0] = 0;
277 		}
278 		val += strlen(name);
279 		if (is_float) (*float_val) = atof(val);
280 		else (*int_val) = atoi(val);
281 		if (sep) sep[0] = c;
282 	}
283 }
284 
filelist_next_url(GF_FileListCtx * ctx,char szURL[GF_MAX_PATH])285 Bool filelist_next_url(GF_FileListCtx *ctx, char szURL[GF_MAX_PATH])
286 {
287 	u32 len;
288 	Bool last_found = GF_FALSE;
289 	FILE *f;
290 	u32 nb_repeat=0, lineno=0;
291 	Double start=0, stop=0;
292 
293 	if (ctx->file_list) {
294 		FileListEntry *fentry, *next;
295 
296 		if (ctx->revert) {
297 			if (!ctx->file_list_idx) {
298 				if (!ctx->floop) return GF_FALSE;
299 				if (ctx->floop>0) ctx->floop--;
300 				ctx->file_list_idx = gf_list_count(ctx->file_list);
301 			}
302 			ctx->file_list_idx --;
303 		} else {
304 			ctx->file_list_idx ++;
305 			if (ctx->file_list_idx >= (s32) gf_list_count(ctx->file_list)) {
306 				if (!ctx->floop) return GF_FALSE;
307 				if (ctx->floop>0) ctx->floop--;
308 				ctx->file_list_idx = 0;
309 			}
310 		}
311 		fentry = gf_list_get(ctx->file_list, ctx->file_list_idx);
312 		strncpy(szURL, fentry->file_name, sizeof(char)*(GF_MAX_PATH-1) );
313 		szURL[GF_MAX_PATH-1] = 0;
314 		next = gf_list_get(ctx->file_list, ctx->file_list_idx + 1);
315 		if (next)
316 			ctx->current_file_dur = next->last_mod_time - fentry->last_mod_time;
317 		return GF_TRUE;
318 	}
319 
320 
321 	f = gf_fopen(ctx->file_path, "rt");
322 	while (f) {
323 		u32 crc;
324 		char *l = gf_fgets(szURL, GF_MAX_PATH, f);
325 		if (!l || gf_feof(f)) {
326 			if (ctx->floop != 0) {
327 				gf_fseek(f, 0, SEEK_SET);
328 				//load first line
329 				last_found = GF_TRUE;
330 				continue;
331 			}
332 			gf_fclose(f);
333 			return GF_FALSE;
334 		}
335 		lineno++;
336 
337 		len = (u32) strlen(szURL);
338 		while (len && strchr("\n\r\t ", szURL[len-1])) {
339 			szURL[len-1] = 0;
340 			len--;
341 		}
342 		if (!len) continue;
343 
344 		//comment
345 		if (szURL[0] == '#') {
346 			nb_repeat=0;
347 			start=stop=0;
348 			filelist_parse_arg(szURL, "repeat=", GF_FALSE, &nb_repeat, NULL);
349 			filelist_parse_arg(szURL, "start=", GF_TRUE, NULL, &start);
350 			filelist_parse_arg(szURL, "stop=", GF_TRUE, NULL, &stop);
351 			strcpy(ctx->szCom, szURL);
352 			continue;
353 		}
354 		crc = gf_crc_32(szURL, (u32) strlen(szURL) );
355 		if (!ctx->last_url_crc) {
356 			ctx->last_url_crc = crc;
357 			ctx->last_url_lineno = lineno;
358 			break;
359 		}
360 		if ((ctx->last_url_crc == crc) && (ctx->last_url_lineno == lineno)) {
361 			last_found = GF_TRUE;
362 			nb_repeat=0;
363 			start=stop=0;
364 			ctx->szCom[0] = 0;
365 			continue;
366 		}
367 		if (last_found) {
368 			ctx->last_url_crc = crc;
369 			ctx->last_url_lineno = lineno;
370 			break;
371 		}
372 		nb_repeat=0;
373 		start=stop=0;
374 		ctx->szCom[0] = 0;
375 	}
376 	gf_fclose(f);
377 
378 	len = (u32) strlen(szURL);
379 	while (len && strchr("\r\n", szURL[len-1])) {
380 		szURL[len-1] = 0;
381 		len--;
382 	}
383 	ctx->nb_repeat = nb_repeat;
384 	ctx->start = start;
385 	ctx->stop = stop;
386 	return GF_TRUE;
387 }
388 
filelist_process(GF_Filter * filter)389 GF_Err filelist_process(GF_Filter *filter)
390 {
391 	Bool start, end;
392 	GF_Err e;
393 	u32 i, count, nb_done, nb_inactive;
394 	FileListPid *iopid;
395 	GF_FileListCtx *ctx = gf_filter_get_udta(filter);
396 
397 	if (ctx->is_eos)
398 		return GF_EOS;
399 
400 	if (!ctx->file_list) {
401 		GF_FilterPacket *pck;
402 		if (!ctx->file_pid) {
403 			return GF_EOS;
404 		}
405 		pck = gf_filter_pid_get_packet(ctx->file_pid);
406 		if (pck) {
407 			gf_filter_pck_get_framing(pck, &start, &end);
408 			gf_filter_pid_drop_packet(ctx->file_pid);
409 
410 			if (end) {
411 				const GF_PropertyValue *p;
412 				Bool is_first = GF_TRUE;
413 				FILE *f=NULL;
414 				p = gf_filter_pid_get_property(ctx->file_pid, GF_PROP_PID_FILEPATH);
415 				if (p) {
416 					if (ctx->file_path) {
417 						gf_free(ctx->file_path);
418 						is_first = GF_FALSE;
419 					}
420 					ctx->file_path = gf_strdup(p->value.string);
421 					f = gf_fopen(p->value.string, "rt");
422 				}
423 				if (!f) {
424 					GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[FileList] Unable to open file %s\n", ctx->file_path ? ctx->file_path : "no source path"));
425 					return GF_SERVICE_ERROR;
426 				} else {
427 					gf_fclose(f);
428 					ctx->load_next = is_first;
429 				}
430 			}
431 		}
432 	}
433 
434 	count = gf_list_count(ctx->io_pids);
435 
436 	if (ctx->load_next) {
437 		GF_Filter *fsrc;
438 		char *url;
439 		char szURL[GF_MAX_PATH];
440 
441 		while (gf_list_count(ctx->filter_srcs)) {
442 			fsrc = gf_list_pop_back(ctx->filter_srcs);
443 			gf_filter_remove_src(filter, fsrc);
444 		}
445 		ctx->load_next = GF_FALSE;
446 
447 		if (! filelist_next_url(ctx, szURL)) {
448 			for (i=0; i<count; i++) {
449 				iopid = gf_list_get(ctx->io_pids, i);
450 				gf_filter_pid_set_eos(iopid->opid);
451 				iopid->ipid = NULL;
452 			}
453 			ctx->is_eos = GF_TRUE;
454 			return GF_EOS;
455 		}
456 		url = szURL;
457 		while (url) {
458 			char *sep = strstr(url, " && ");
459 			if (sep) sep[0] = 0;
460 
461 			fsrc = gf_filter_connect_source(filter, url, ctx->file_path, &e);
462 			if (e) {
463 				if (sep) sep[0] = ' ';
464 				GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[FileList] Failed to open file %s: %s\n", szURL, gf_error_to_string(e) ));
465 				return GF_SERVICE_ERROR;
466 			}
467 			gf_list_add(ctx->filter_srcs, fsrc);
468 
469 			if (!sep) break;
470 			sep[0] = ' ';
471 			url = sep+4;
472 		}
473 		//wait for PIDs to connect
474 		GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("[FileList] Switching to file %s\n", szURL));
475 	}
476 
477 	//init first timestamp
478 	if (!ctx->dts_sub_plus_one) {
479 		for (i=0; i<count; i++) {
480 			GF_FilterPacket *pck;
481 			u64 dts;
482 			iopid = gf_list_get(ctx->io_pids, i);
483 			if (!iopid->ipid) return GF_OK;
484 			pck = gf_filter_pid_get_packet(iopid->ipid);
485 			if (!pck) return GF_OK;
486 
487 			dts = gf_filter_pck_get_dts(pck);
488 			if (dts==GF_FILTER_NO_TS) dts=0;
489 
490 			dts *= 1000000;
491 			dts /= iopid->timescale;
492 			if (!ctx->dts_sub_plus_one) ctx->dts_sub_plus_one = dts + 1;
493 			else if (dts < ctx->dts_sub_plus_one - 1) ctx->dts_sub_plus_one = dts + 1;
494 	 	}
495 		for (i=0; i<count; i++) {
496 			iopid = gf_list_get(ctx->io_pids, i);
497 			iopid->dts_sub = ctx->dts_sub_plus_one - 1;
498 			iopid->dts_sub *= iopid->o_timescale;
499 			iopid->dts_sub /= 1000000;
500 		}
501 	}
502 
503 	nb_done = nb_inactive = 0;
504 	for (i=0; i<count; i++) {
505 		iopid = gf_list_get(ctx->io_pids, i);
506 		if (!iopid->ipid) {
507 			nb_inactive++;
508 			continue;
509 		}
510 		if (iopid->is_eos) {
511 			nb_done++;
512 			continue;
513 		}
514 		if (!iopid->is_playing) {
515 			continue;
516 		}
517 		if (gf_filter_pid_would_block(iopid->opid))
518 			continue;
519 
520 		while (1) {
521 			GF_FilterPacket *dst_pck;
522 			u64 cts, dts;
523 			u32 dur;
524 			GF_FilterPacket *pck;
525 
526 			pck = gf_filter_pid_get_packet(iopid->ipid);
527 			if (!pck) {
528 				if (gf_filter_pid_is_eos(iopid->ipid))
529 					iopid->is_eos = GF_TRUE;
530 
531 				if (iopid->is_eos)
532 					nb_done++;
533 				break;
534 			}
535 
536 			if (gf_filter_pid_would_block(iopid->opid))
537 				break;
538 
539 			dst_pck = gf_filter_pck_new_ref(iopid->opid, NULL, 0, pck);
540 			gf_filter_pck_merge_properties(pck, dst_pck);
541 			dts = gf_filter_pck_get_dts(pck);
542 			if (dts==GF_FILTER_NO_TS) dts=0;
543 
544 			cts = gf_filter_pck_get_cts(pck);
545 			if (cts==GF_FILTER_NO_TS) cts=0;
546 
547 			if (iopid->single_frame && (ctx->fsort==FL_SORT_DATEX) ) {
548 				dur = (u32) ctx->current_file_dur;
549 				//move from second to input pid timescale
550 				dur *= iopid->timescale;
551 			} else if (iopid->single_frame && ctx->fdur.num && ctx->fdur.den) {
552 				s64 pdur = ctx->fdur.num;
553 				pdur *= iopid->timescale;
554 				pdur /= ctx->fdur.den;
555 				dur = (u32) pdur;
556 			} else {
557 				dur = gf_filter_pck_get_duration(pck);
558 			}
559 
560 			if (iopid->timescale == iopid->o_timescale) {
561 				gf_filter_pck_set_dts(dst_pck, iopid->dts_o + dts - iopid->dts_sub);
562 				gf_filter_pck_set_cts(dst_pck, iopid->cts_o + cts - iopid->dts_sub);
563 				gf_filter_pck_set_duration(dst_pck, dur);
564 			} else {
565 				u64 ts = dts;
566 				ts *= iopid->o_timescale;
567 				ts /= iopid->timescale;
568 				gf_filter_pck_set_dts(dst_pck, iopid->dts_o + ts - iopid->dts_sub);
569 				ts = cts;
570 				ts *= iopid->o_timescale;
571 				ts /= iopid->timescale;
572 				gf_filter_pck_set_cts(dst_pck, iopid->cts_o + ts - iopid->dts_sub);
573 
574 				ts = dur;
575 				ts *= iopid->o_timescale;
576 				ts /= iopid->timescale;
577 				gf_filter_pck_set_duration(dst_pck, (u32) ts);
578 			}
579 			dts += dur;
580 			cts += dur;
581 			if (dts > iopid->max_dts) iopid->max_dts = dts;
582 			if (cts > iopid->max_cts) iopid->max_cts = cts;
583 
584 			//remember our first DTS
585 			if (!iopid->first_dts_plus_one) {
586 				iopid->first_dts_plus_one = dts + 1;
587 			}
588 			gf_filter_pck_send(dst_pck);
589 			gf_filter_pid_drop_packet(iopid->ipid);
590 			//if we have an end range, compute max_dts (includes dur) - firrst_dts
591 			if (ctx->stop>ctx->start) {
592 				if ( (ctx->stop-ctx->start) * iopid->timescale <= (iopid->max_dts - iopid->first_dts_plus_one + 1)) {
593 					iopid->is_eos = GF_TRUE;
594 					nb_done++;
595 					break;
596 				}
597 			}
598 		}
599 	}
600 	if ((nb_inactive!=count) && (nb_done+nb_inactive==count)) {
601 		//compute max cts and dts in 1Mhz timescale
602 		u64 max_cts = 0, max_dts = 0;
603 
604 		if (gf_filter_end_of_session(filter)) {
605 			for (i=0; i<count; i++) {
606 				iopid = gf_list_get(ctx->io_pids, i);
607 				gf_filter_pid_set_eos(iopid->opid);
608 			}
609 			ctx->is_eos = GF_TRUE;
610 			return GF_EOS;
611 		}
612 		ctx->dts_sub_plus_one = 0;
613 		for (i=0; i<count; i++) {
614 			iopid = gf_list_get(ctx->io_pids, i);
615 			if (iopid->ipid) {
616 				u64 ts;
617 				ts = iopid->max_cts - iopid->dts_sub;
618 				ts *= 1000000;
619 				ts /= iopid->timescale;
620 				if (max_cts < ts) max_cts = ts;
621 
622 				ts = iopid->max_dts - iopid->dts_sub;
623 				ts *= 1000000;
624 				ts /= iopid->timescale;
625 				if (max_dts < ts) max_dts = ts;
626 			}
627 		}
628 		ctx->cts_offset += max_dts;
629 		ctx->dts_offset += max_dts;
630 
631 		if (ctx->nb_repeat) {
632 			ctx->nb_repeat--;
633 			for (i=0; i<count; i++) {
634 				GF_FilterEvent evt;
635 				iopid = gf_list_get(ctx->io_pids, i);
636 				if (!iopid->ipid) continue;
637 
638 				GF_FEVT_INIT(evt, GF_FEVT_STOP, iopid->ipid);
639 				gf_filter_pid_send_event(iopid->ipid, &evt);
640 
641 				iopid->is_eos = GF_FALSE;
642 				filelist_start_ipid(ctx, iopid);
643 			}
644 		} else {
645 			//detach all input pids and
646 			for (i=0; i<count; i++) {
647 				iopid = gf_list_get(ctx->io_pids, i);
648 				iopid->ipid = NULL;
649 			}
650 			//force load
651 			ctx->load_next = GF_TRUE;
652 			return filelist_process(filter);
653 		}
654 	}
655 	return GF_OK;
656 }
657 
filelist_add_entry(GF_FileListCtx * ctx,FileListEntry * fentry)658 void filelist_add_entry(GF_FileListCtx *ctx, FileListEntry *fentry)
659 {
660 	u32 i, count;
661 	GF_LOG(GF_LOG_DEBUG, GF_LOG_AUTHOR, ("[FileList] Adding file %s to list\n", fentry->file_name));
662 	if (ctx->fsort==FL_SORT_NONE) {
663 		gf_list_add(ctx->file_list, fentry);
664 		return;
665 	}
666 	count = gf_list_count(ctx->file_list);
667 	for (i=0; i<count; i++) {
668 		Bool insert=GF_FALSE;
669 		FileListEntry *cur = gf_list_get(ctx->file_list, i);
670 		switch (ctx->fsort) {
671 		case FL_SORT_SIZE:
672 			if (cur->file_size>fentry->file_size) insert = GF_TRUE;
673 			break;
674 		case FL_SORT_DATE:
675 		case FL_SORT_DATEX:
676 			if (cur->last_mod_time>fentry->last_mod_time) insert = GF_TRUE;
677 			break;
678 		case FL_SORT_NAME:
679 			if (strcmp(cur->file_name, fentry->file_name) > 0) insert = GF_TRUE;
680 			break;
681 		}
682 		if (insert) {
683 			gf_list_insert(ctx->file_list, fentry, i);
684 			return;
685 		}
686 	}
687 	gf_list_add(ctx->file_list, fentry);
688 }
689 
filelist_enum(void * cbck,char * item_name,char * item_path,GF_FileEnumInfo * file_info)690 Bool filelist_enum(void *cbck, char *item_name, char *item_path, GF_FileEnumInfo *file_info)
691 {
692 	FileListEntry *fentry;
693 	GF_FileListCtx *ctx = cbck;
694 	if (file_info->hidden) return GF_FALSE;
695 	if (file_info->directory) return GF_FALSE;
696 	if (file_info->drive) return GF_FALSE;
697 	if (file_info->system) return GF_FALSE;
698 
699 	GF_SAFEALLOC(fentry, FileListEntry);
700 	if (!fentry) return GF_TRUE;
701 
702 	fentry->file_name = gf_strdup(item_path);
703 	fentry->file_size = file_info->size;
704 	fentry->last_mod_time = file_info->last_modified;
705 	filelist_add_entry(ctx, fentry);
706 
707 	return GF_FALSE;
708 }
709 
filelist_initialize(GF_Filter * filter)710 GF_Err filelist_initialize(GF_Filter *filter)
711 {
712 	u32 i, count;
713 	char *sep_dir, c=0, *dir, *pattern;
714 	GF_FileListCtx *ctx = gf_filter_get_udta(filter);
715 	ctx->io_pids = gf_list_new();
716 
717 	if (!ctx->srcs || !gf_list_count(ctx->srcs)) {
718 		GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("[FileList] No inputs\n"));
719 		//not completely correct, needs further testing
720 #if 0
721 		if (!gf_filter_connections_pending(filter)) {
722 			GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[FileList] No source specified and no input PIDs pending, cannot instantiate\n"));
723 			return GF_BAD_PARAM;
724 		}
725 #endif
726 		return GF_OK;
727 	}
728 	ctx->filter_srcs = gf_list_new();
729 	ctx->file_list = gf_list_new();
730 	count = gf_list_count(ctx->srcs);
731 	for (i=0; i<count; i++) {
732 		char *list = gf_list_get(ctx->srcs, i);
733 
734 		if (strchr(list, '*') ) {
735 			sep_dir = strrchr(list, '/');
736 			if (!sep_dir) sep_dir = strrchr(list, '\\');
737 			if (sep_dir) {
738 				c = sep_dir[0];
739 				sep_dir[0] = 0;
740 				dir = list;
741 				pattern = sep_dir+2;
742 			} else {
743 				dir = ".";
744 				pattern = list;
745 			}
746 			gf_enum_directory(dir, GF_FALSE, filelist_enum, ctx, pattern);
747 			if (c && sep_dir) sep_dir[0] = c;
748 		} else {
749 			if (gf_file_exists(list)) {
750 				FileListEntry *fentry;
751 				GF_SAFEALLOC(fentry, FileListEntry);
752 				if (fentry) {
753 					FILE *fo;
754 					fentry->file_name = gf_strdup(list);
755 					fentry->last_mod_time = gf_file_modification_time(list);
756 					fo = gf_fopen(list, "rb");
757 					if (fo) {
758 						fentry->file_size = gf_fsize(fo);
759 						gf_fclose(fo);
760 					}
761 					filelist_add_entry(ctx, fentry);
762 				}
763 			} else {
764 				GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[FileList] File %s not found, ignoring\n", list));
765 			}
766 		}
767 	}
768 
769 	if (!gf_list_count(ctx->file_list)) {
770 		GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[FileList] No files found in list %s\n", ctx->srcs));
771 		return GF_BAD_PARAM;
772 	}
773 	if (ctx->fsort==FL_SORT_DATEX) {
774 		ctx->revert = GF_FALSE;
775 		ctx->floop = 0;
776 	}
777 	ctx->file_list_idx = ctx->revert ? gf_list_count(ctx->file_list) : -1;
778 	ctx->load_next = GF_TRUE;
779 	//from now on we only accept the above caps
780 	gf_filter_override_caps(filter, FileListCapsSrc, 2);
781 	//and we act as a source, request processing
782 	gf_filter_post_process_task(filter);
783 	//prevent deconnection of filter when no input
784 	gf_filter_make_sticky(filter);
785 	return GF_OK;
786 }
787 
filelist_finalize(GF_Filter * filter)788 void filelist_finalize(GF_Filter *filter)
789 {
790 	GF_FileListCtx *ctx = gf_filter_get_udta(filter);
791 	while (gf_list_count(ctx->io_pids)) {
792 		FileListPid *iopid = gf_list_pop_back(ctx->io_pids);
793 		gf_free(iopid);
794 	}
795 	if (ctx->file_list) {
796 		while (gf_list_count(ctx->file_list)) {
797 			FileListEntry *fentry = gf_list_pop_back(ctx->file_list);
798 			gf_free(fentry->file_name);
799 			gf_free(fentry);
800 		}
801 		gf_list_del(ctx->file_list);
802 	}
803 	gf_list_del(ctx->io_pids);
804 	gf_list_del(ctx->filter_srcs);
805 	if (ctx->file_path) gf_free(ctx->file_path);
806 }
807 
808 
809 #define OFFS(_n)	#_n, offsetof(GF_FileListCtx, _n)
810 static const GF_FilterArgs GF_FileListArgs[] =
811 {
812 	{ OFFS(floop), "loop playlist/list of files, `0` for one time, `n` for n+1 times, `-1` for indefinitely", GF_PROP_SINT, "0", NULL, 0},
813 	{ OFFS(srcs), "list of files to play - see filter help", GF_PROP_STRING_LIST, NULL, NULL, 0},
814 	{ OFFS(fdur), "for source files with a single frame, sets frame duration. 0/NaN fraction means reuse source timing which is usually not set!", GF_PROP_FRACTION, "1/25", NULL, 0},
815 	{ OFFS(revert), "revert list of files (not playlist)", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
816 	{ OFFS(timescale), "force output timescale on all pids. 0 uses the timescale of the first pid found", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED},
817 
818 	{ OFFS(fsort), "sort list of files\n"
819 		"- no: no sorting, use default directory enumeration of OS\n"
820 		"- name: sort by alphabetical name\n"
821 		"- size: sort by increasing size\n"
822 		"- date: sort by increasing modification time\n"
823 		"- datex: sort by increasing modification time - see filter help"
824 		, GF_PROP_UINT, "no", "no|name|size|date|datex", 0},
825 	{0}
826 };
827 
828 
829 static const GF_FilterCapability FileListCaps[] =
830 {
831 	CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
832 	CAP_STRING(GF_CAPS_INPUT, GF_PROP_PID_FILE_EXT, "txt|m3u"),
833 	CAP_UINT(GF_CAPS_OUTPUT_EXCLUDED, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
834 };
835 
836 
837 GF_FilterRegister FileListRegister = {
838 	.name = "flist",
839 	GF_FS_SET_DESCRIPTION("Sources concatenator")
840 	GF_FS_SET_HELP("This filter can be used to play playlist files or a list of sources.\n"
841 		"\n"
842 		"The filter loads any source supported by GPAC: remote or local files or streaming sessions (TS, RTP, DASH or other).\n"
843 		"The filter forces input demultiplex and recomputes the input timestamps into a continuous timeline.\n"
844 		"At each new source, the filter tries to remap input PIDs to already declared output PIDs of the same type, if any, or declares new output PIDs otherwise. If no input PID matches the type of an output, no packets are send for that PID.\n"
845 		"\n"
846 		"# Source list mode\n"
847 		"The source list mode is activated by using `flist:srcs=f1[,f2]`, where f1 can be a file or a directory to enum.\n"
848 		"The syntax for directory enum is:\n"
849 		"- dir/*: enumerates everything in dir\n"
850 		"- foo/*.png: enumerates all files with extension png in foo\n"
851 		"- foo/*.png;*.jpg: enumerates all files with extension png or jpg in foo\n"
852 		"\n"
853 		"The resulting file list can be sorted using [-fsort]().\n"
854 		"If the sort mode is `datex` and source files are images or single frame files, the following applies:\n"
855 		"- options [-floop](), [-revert]() and [-dur]() are ignored\n"
856 		"- the files are sorted by modification time\n"
857 		"- the first frame is assigned a timestamp of 0\n"
858 		"- each frame (coming from each file) is assigned a duration equal to the difference of modification time between the file and the next file\n"
859 		"- the last frame is assigned the same duration as the previous one\n"
860 		"\n"
861 		"# Playlist mode\n"
862 		"The playlist mode is activated when opening a playlist file (extension txt or m3u).\n"
863 		"In this mode, directives can be given in a comment line, i.e. a line starting with '#' before the line with the file name.\n"
864 		"The following directives, separated with space or comma, are supported:\n"
865 		"- repeat=N: repeats N times the content (hence played N+1)\n"
866 		"- start=T: tries to play the file from start time T seconds (double format only)\n"
867 		"Warning: This may not work with some files/formats not supporting seeking\n"
868 		"- stop=T: stops source playback after T seconds (double format only)\n"
869 		"This works on any source (implemented independently from seek support).\n"
870 		"\n"
871 		"The source lines follow the usual source syntax, see `gpac -h`.\n"
872 		"Additional pid properties can be added per source (see `gpac -h doc`), but are valid only for the current source, and reset at next source.\n"
873 		"\n"
874 		"The URL given can either be a single URL, or a list of URLs separated by \" && \" to load several sources for the active entry.\n"
875 		"EX audio.mp4 && video.mp4\n"
876 		"\n"
877 		"The playlist file is refreshed whenever the next source has to be reloaded in order to allow for dynamic pushing of sources in the playlist.\n"\
878 		"If the last URL played cannot be found in the playlist, the first URL in the playlist file will be loaded.\n")
879 	.private_size = sizeof(GF_FileListCtx),
880 	.max_extra_pids = -1,
881 	.flags = GF_FS_REG_ACT_AS_SOURCE | GF_FS_REG_REQUIRES_RESOLVER,
882 	.args = GF_FileListArgs,
883 	.initialize = filelist_initialize,
884 	.finalize = filelist_finalize,
885 	SETCAPS(FileListCaps),
886 	.configure_pid = filelist_configure_pid,
887 	.process = filelist_process,
888 	.process_event = filelist_process_event
889 };
890 
filelist_register(GF_FilterSession * session)891 const GF_FilterRegister *filelist_register(GF_FilterSession *session)
892 {
893 	return &FileListRegister;
894 }
895 
896