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