1 /*
2  *			GPAC - Multimedia Framework C SDK
3  *
4  *			Authors: Jean Le Feuvre
5  *			Copyright (c) Telecom ParisTech 2017-2020
6  *					All rights reserved
7  *
8  *  This file is part of GPAC / generic FILE input 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 
27 #include <gpac/filters.h>
28 #include <gpac/constants.h>
29 
30 enum{
31 	FILE_RAND_NONE=0,
32 	FILE_RAND_ANY,
33 	FILE_RAND_SC_ANY,
34 	FILE_RAND_SC_AVC,
35 	FILE_RAND_SC_HEVC,
36 	FILE_RAND_SC_AV1
37 };
38 
39 typedef struct
40 {
41 	//options
42 	char *src;
43 	char *ext, *mime;
44 	u32 block_size;
45 	GF_Fraction64 range;
46 
47 	//only one output pid declared
48 	GF_FilterPid *pid;
49 
50 	FILE *file;
51 	u64 file_size;
52 	u64 file_pos, end_pos;
53 	Bool is_end, pck_out;
54 	Bool is_null;
55 	Bool full_file_only;
56 	Bool do_reconfigure;
57 	char *block;
58 	u32 is_random;
59 	Bool cached_set;
60 	Bool no_failure;
61 } GF_FileInCtx;
62 
63 
filein_initialize(GF_Filter * filter)64 static GF_Err filein_initialize(GF_Filter *filter)
65 {
66 	GF_FileInCtx *ctx = (GF_FileInCtx *) gf_filter_get_udta(filter);
67 	FILE *old_file = NULL;
68 	char *ext_start = NULL;
69 	char *frag_par = NULL;
70 	char *cgi_par = NULL;
71 	char *src, *path;
72 	const char *prev_url=NULL;
73 
74 	if (!ctx || !ctx->src) return GF_BAD_PARAM;
75 
76 	if (!strcmp(ctx->src, "null")) {
77 		ctx->pid = gf_filter_pid_new(filter);
78 		gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE));
79 		gf_filter_pid_set_eos(ctx->pid);
80 		ctx->is_end = GF_TRUE;
81 		return GF_OK;
82 	}
83 	if (!strcmp(ctx->src, "rand") || !strcmp(ctx->src, "randsc")) {
84 		gf_rand_init(GF_FALSE);
85 		ctx->is_random = FILE_RAND_ANY;
86 		if (!strcmp(ctx->src, "randsc")) {
87 			ctx->is_random = FILE_RAND_SC_ANY;
88 			if (ctx->mime) {
89 				if (!strcmp(ctx->mime, "video/avc")) ctx->is_random = FILE_RAND_SC_AVC;
90 				if (!strcmp(ctx->mime, "video/hevc")) ctx->is_random = FILE_RAND_SC_HEVC;
91 				if (!strcmp(ctx->mime, "video/av1")) ctx->is_random = FILE_RAND_SC_AV1;
92 			}
93 		}
94 
95 		if (!ctx->block_size) ctx->block_size = 5000;
96 		while (ctx->block_size % 4) ctx->block_size++;
97 		ctx->block = gf_malloc(ctx->block_size +1);
98 		return GF_OK;
99 	}
100 
101 
102 
103 	if (strnicmp(ctx->src, "file:/", 6) && strnicmp(ctx->src, "gfio:/", 6) && strstr(ctx->src, "://"))  {
104 		gf_filter_setup_failure(filter, GF_NOT_SUPPORTED);
105 		return GF_NOT_SUPPORTED;
106 	}
107 	path = strstr(ctx->src, "://");
108 	if (path) path += 3;
109 	if (path && strstr(path, "://")) {
110 		ctx->is_end = GF_TRUE;
111 		return gf_filter_pid_raw_new(filter, path, path, NULL, NULL, NULL, 0, GF_TRUE, &ctx->pid);
112 	}
113 
114 	//local file
115 
116 	//strip any fragment identifer
117 	ext_start = gf_file_ext_start(ctx->src);
118 	frag_par = strchr(ext_start ? ext_start : ctx->src, '#');
119 	if (frag_par) frag_par[0] = 0;
120 	cgi_par = strchr(ctx->src, '?');
121 	if (cgi_par) cgi_par[0] = 0;
122 
123 	src = (char *) ctx->src;
124 	if (!strnicmp(ctx->src, "file://", 7)) src += 7;
125 	else if (!strnicmp(ctx->src, "file:", 5)) src += 5;
126 
127 	//for gfio, do not close file until we open the new one
128 	if (ctx->do_reconfigure) {
129 		old_file = ctx->file;
130 		ctx->file = NULL;
131 		if (gf_fileio_check(old_file))
132 			prev_url = gf_fileio_url((GF_FileIO *)old_file);
133 	}
134 
135 	if (!ctx->file) {
136 		ctx->file = gf_fopen_ex(src, prev_url, "rb");
137 	}
138 
139 	if (old_file) {
140 		gf_fclose(old_file);
141 	}
142 
143 	if (!ctx->file) {
144 		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileIn] Failed to open %s\n", src));
145 
146 		if (frag_par) frag_par[0] = '#';
147 		if (cgi_par) cgi_par[0] = '?';
148 
149 		if (ctx->no_failure) {
150 			gf_filter_notification_failure(filter, GF_URL_ERROR, GF_FALSE);
151 			ctx->is_end = GF_TRUE;
152 			return GF_OK;
153 		}
154 
155 		gf_filter_setup_failure(filter, GF_URL_ERROR);
156 #ifdef GPAC_ENABLE_COVERAGE
157 		if (gf_sys_is_cov_mode() && !strcmp(src, "blob"))
158 			return GF_OK;
159 #endif
160 		return GF_URL_ERROR;
161 	}
162 	GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[FileIn] opening %s\n", src));
163 	ctx->file_size = gf_fsize(ctx->file);
164 
165 	ctx->cached_set = GF_FALSE;
166 	ctx->full_file_only = GF_FALSE;
167 
168 	if (ctx->do_reconfigure && gf_fileio_check(ctx->file)) {
169 		GF_FileIO *gfio = (GF_FileIO *)ctx->file;
170 		gf_free(ctx->src);
171 		ctx->src = gf_strdup(gf_fileio_url(gfio));
172 	}
173 
174 
175 	ctx->file_pos = ctx->range.num;
176 	if (ctx->range.den) {
177 		ctx->end_pos = ctx->range.den;
178 		if (ctx->end_pos>ctx->file_size) {
179 			ctx->range.den = ctx->end_pos = ctx->file_size;
180 		}
181 	}
182 	gf_fseek(ctx->file, ctx->file_pos, SEEK_SET);
183 	ctx->is_end = GF_FALSE;
184 
185 	if (frag_par) frag_par[0] = '#';
186 	if (cgi_par) cgi_par[0] = '?';
187 
188 	if (!ctx->block) {
189 		if (!ctx->block_size) {
190 			if (ctx->file_size>500000000) ctx->block_size = 1000000;
191 			else ctx->block_size = 5000;
192 		}
193 		ctx->block = gf_malloc(ctx->block_size +1);
194 	}
195 	return GF_OK;
196 }
197 
filein_finalize(GF_Filter * filter)198 static void filein_finalize(GF_Filter *filter)
199 {
200 	GF_FileInCtx *ctx = (GF_FileInCtx *) gf_filter_get_udta(filter);
201 
202 	if (ctx->file) gf_fclose(ctx->file);
203 	if (ctx->block) gf_free(ctx->block);
204 }
205 
filein_probe_url(const char * url,const char * mime_type)206 static GF_FilterProbeScore filein_probe_url(const char *url, const char *mime_type)
207 {
208 	char *ext_start = NULL;
209 	char *frag_par = NULL;
210 	char *cgi_par = NULL;
211 	char *src = (char *) url;
212 	Bool res;
213 
214 	if (!strcmp(url, "-") || !strcmp(url, "stdin")) return GF_FPROBE_NOT_SUPPORTED;
215 
216 	if (!strnicmp(url, "file://", 7)) src += 7;
217 	else if (!strnicmp(url, "file:", 5)) src += 5;
218 
219 	if (!strcmp(url, "null")) return GF_FPROBE_SUPPORTED;
220 	if (!strcmp(url, "rand")) return GF_FPROBE_SUPPORTED;
221 	if (!strcmp(url, "randsc")) return GF_FPROBE_SUPPORTED;
222 	if (!strncmp(url, "gfio://", 7)) {
223 		GF_FileIO *gfio = gf_fileio_from_url(url);
224 		if (gfio && gf_fileio_read_mode(gfio))
225 			return GF_FPROBE_SUPPORTED;
226 		return GF_FPROBE_NOT_SUPPORTED;
227 	}
228 
229 	//strip any fragment identifer
230 	ext_start = gf_file_ext_start(url);
231 	frag_par = strchr(ext_start ? ext_start : url, '#');
232 	if (frag_par) frag_par[0] = 0;
233 	cgi_par = strchr(url, '?');
234 	if (cgi_par) cgi_par[0] = 0;
235 
236 	res = gf_file_exists(src);
237 
238 	if (frag_par) frag_par[0] = '#';
239 	if (cgi_par) cgi_par[0] = '?';
240 
241 	return res ? GF_FPROBE_SUPPORTED : GF_FPROBE_NOT_SUPPORTED;
242 }
243 
filein_process_event(GF_Filter * filter,const GF_FilterEvent * evt)244 static Bool filein_process_event(GF_Filter *filter, const GF_FilterEvent *evt)
245 {
246 	GF_FileInCtx *ctx = (GF_FileInCtx *) gf_filter_get_udta(filter);
247 
248 	if (evt->base.on_pid && (evt->base.on_pid != ctx->pid))
249 		return GF_FALSE;
250 
251 	switch (evt->base.type) {
252 	case GF_FEVT_PLAY:
253 	case GF_FEVT_PLAY_HINT:
254 		ctx->full_file_only = evt->play.full_file_only;
255 		return GF_TRUE;
256 	case GF_FEVT_STOP:
257 		//stop sending data
258 		ctx->is_end = GF_TRUE;
259 		gf_filter_pid_set_eos(ctx->pid);
260 		return GF_TRUE;
261 	case GF_FEVT_SOURCE_SEEK:
262 		if (ctx->is_random)
263 			return GF_TRUE;
264 		if (ctx->file_size && (evt->seek.start_offset >= ctx->file_size)) {
265 			GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileIn] Seek request outside of file %s range ("LLU" vs size "LLU")\n", ctx->src, evt->seek.start_offset, ctx->file_size));
266 			return GF_TRUE;
267 		}
268 
269 		GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[FileIn] Asked to seek source to range "LLU"-"LLU"\n", evt->seek.start_offset, evt->seek.end_offset));
270 		ctx->is_end = GF_FALSE;
271 
272 		if (gf_fileio_check(ctx->file)) {
273 			ctx->cached_set = GF_FALSE;
274 		}
275 
276 		if (ctx->file) {
277 			int res = gf_fseek(ctx->file, evt->seek.start_offset, SEEK_SET);
278 			if (res) {
279 				GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileIn] Seek on file failed: %d\n", res));
280 				return GF_TRUE;
281 			}
282 		}
283 
284 		ctx->file_pos = evt->seek.start_offset;
285 		ctx->end_pos = evt->seek.end_offset;
286 		if (ctx->end_pos>ctx->file_size) ctx->end_pos = ctx->file_size;
287 		ctx->range.num = evt->seek.start_offset;
288 		ctx->range.den = ctx->end_pos;
289 		if (evt->seek.hint_block_size > ctx->block_size) {
290 			ctx->block_size = evt->seek.hint_block_size;
291 			ctx->block = gf_realloc(ctx->block, ctx->block_size+1);
292 		}
293 		return GF_TRUE;
294 	case GF_FEVT_SOURCE_SWITCH:
295 		if (ctx->is_random)
296 			return GF_TRUE;
297 		GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[FileIn] Asked to switch source to %s (range "LLU"-"LLU")\n", evt->seek.source_switch ? evt->seek.source_switch : "self", evt->seek.start_offset, evt->seek.end_offset));
298 		assert(ctx->is_end);
299 		ctx->range.num = evt->seek.start_offset;
300 		ctx->range.den = evt->seek.end_offset;
301 		if (evt->seek.source_switch) {
302 			if (strcmp(evt->seek.source_switch, ctx->src)) {
303 				gf_free(ctx->src);
304 				ctx->src = gf_strdup(evt->seek.source_switch);
305 			}
306 			ctx->do_reconfigure = GF_TRUE;
307 		}
308 		//don't send a setup failure on source switch (this would destroy ourselves which we don't want in DASH)
309 		ctx->no_failure = GF_TRUE;
310 		filein_initialize(filter);
311 		gf_filter_post_process_task(filter);
312 		break;
313 	default:
314 		break;
315 	}
316 	return GF_FALSE;
317 }
318 
319 
filein_pck_destructor(GF_Filter * filter,GF_FilterPid * pid,GF_FilterPacket * pck)320 static void filein_pck_destructor(GF_Filter *filter, GF_FilterPid *pid, GF_FilterPacket *pck)
321 {
322 	GF_FileInCtx *ctx = (GF_FileInCtx *) gf_filter_get_udta(filter);
323 	ctx->pck_out = GF_FALSE;
324 	//ready to process again
325 	gf_filter_post_process_task(filter);
326 }
327 
filein_process(GF_Filter * filter)328 static GF_Err filein_process(GF_Filter *filter)
329 {
330 	GF_Err e;
331 	u32 nb_read, to_read;
332 	u64 lto_read;
333 	GF_FilterPacket *pck;
334 	GF_FileInCtx *ctx = (GF_FileInCtx *) gf_filter_get_udta(filter);
335 
336 	if (ctx->is_end)
337 		return GF_EOS;
338 
339 	//until packet is released we return EOS (no processing), and ask for processing again upon release
340 	if (ctx->pck_out)
341 		return GF_EOS;
342 	if (ctx->pid && gf_filter_pid_would_block(ctx->pid)) {
343 		assert(0);
344 		return GF_OK;
345 	}
346 
347 	if (ctx->full_file_only && ctx->pid && !ctx->do_reconfigure && ctx->cached_set) {
348 		ctx->is_end = GF_TRUE;
349 		pck = gf_filter_pck_new_shared(ctx->pid, ctx->block, 0, filein_pck_destructor);
350 		gf_filter_pck_set_framing(pck, ctx->file_pos ? GF_FALSE : GF_TRUE, ctx->is_end);
351 		gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1);
352 		ctx->pck_out = GF_TRUE;
353 		gf_filter_pck_send(pck);
354 		gf_filter_pid_set_eos(ctx->pid);
355 
356 		if (ctx->file_size && gf_filter_reporting_enabled(filter)) {
357 			char szStatus[1024], *szSrc;
358 			szSrc = gf_file_basename(ctx->src);
359 
360 			sprintf(szStatus, "%s: EOS (dispatch canceled after "LLD" b, file size "LLD" b)", szSrc, (s64) ctx->file_pos, (s64) ctx->file_size);
361 			gf_filter_update_status(filter, 10000, szStatus);
362 		}
363 		return GF_OK;
364 	}
365 	if (ctx->is_random) {
366 		u32 i;
367 		if (ctx->is_random>=FILE_RAND_SC_ANY) {
368 			for (i=0; i<ctx->block_size; i+= 4) {
369 				u32 val = gf_rand();
370 
371 				if (i+4>=ctx->block_size) {
372 					* ((u32 *) (ctx->block + i)) = val;
373 					continue;
374 				}
375 				if (val % 100) {
376 					* ((u32 *) (ctx->block + i)) = val;
377 					continue;
378 				}
379 				if (ctx->is_random==FILE_RAND_SC_AVC) {
380 					u32 rand_high = val>>24;
381 					* ((u32 *) (ctx->block + i)) = 0x00000001;
382 					i += 4;
383 					val &= 0x00FFFFFF;
384 					rand_high = rand_high%31;
385 					rand_high <<= 24;
386 					val |= rand_high;
387 					* ((u32 *) (ctx->block + i)) = val;
388 				} else if (ctx->is_random==FILE_RAND_SC_HEVC) {
389 					u32 rand_high = val>>16;
390 					* ((u32 *) (ctx->block + i)) = 0x00000001;
391 					i += 4;
392 					val &= 0x0000FFFF;
393 					rand_high = rand_high % 63;
394 					rand_high <<= 8;
395 					rand_high |= 1; //end of layerid (=0) and temporal sublayer (=1)
396 					rand_high <<= 16;
397 					val |= rand_high;
398 					* ((u32 *) (ctx->block + i)) = val;
399 				}
400 				else {
401 					val &= 0x000001FF;
402 					* ((u32 *) (ctx->block + i)) = val;
403 				}
404 			}
405 		} else {
406 			for (i=0; i<ctx->block_size; i+= 4) {
407 				* ((u32 *) (ctx->block + i)) = gf_rand();
408 			}
409 		}
410 		ctx->block[ctx->block_size]=0;
411 
412 		if (!ctx->pid) {
413 			e = gf_filter_pid_raw_new(filter, ctx->src, ctx->src, ctx->mime, ctx->ext, ctx->block, ctx->block_size, GF_TRUE,  &ctx->pid);
414 			if (e) return e;
415 		}
416 		pck = gf_filter_pck_new_shared(ctx->pid, ctx->block, ctx->block_size, filein_pck_destructor);
417 		if (!pck)
418 			return GF_OK;
419 
420 		gf_filter_pck_set_framing(pck, ctx->file_pos ? GF_FALSE : GF_TRUE, GF_FALSE);
421 		gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1);
422 		ctx->file_pos += ctx->block_size;
423 
424 		ctx->pck_out = GF_TRUE;
425 		gf_filter_pck_send(pck);
426 		return GF_OK;
427 	}
428 
429 	//compute size to read as u64 (large file)
430 	if (ctx->end_pos > ctx->file_pos)
431 		lto_read = ctx->end_pos - ctx->file_pos;
432 	else if (ctx->file_size)
433 		lto_read = ctx->file_size - ctx->file_pos;
434 	else
435 		lto_read = ctx->block_size;
436 
437 	//and clamp based on blocksize as u32
438 	if (lto_read > (u64) ctx->block_size)
439 		to_read = (u64) ctx->block_size;
440 	else
441 		to_read = (u32) lto_read;
442 
443 	nb_read = (u32) gf_fread(ctx->block, to_read, ctx->file);
444 	if (!nb_read)
445 		ctx->file_size = ctx->file_pos;
446 
447 	ctx->block[nb_read] = 0;
448 	if (!ctx->pid || ctx->do_reconfigure) {
449 		ctx->do_reconfigure = GF_FALSE;
450 		e = gf_filter_pid_raw_new(filter, ctx->src, ctx->src, ctx->mime, ctx->ext, ctx->block, nb_read, GF_TRUE, &ctx->pid);
451 		if (e) return e;
452 
453 		if (!gf_fileio_check(ctx->file)) {
454 			gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_FILE_CACHED, &PROP_BOOL(GF_TRUE) );
455 
456 			gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_DOWN_SIZE, ctx->file_size ? &PROP_LONGUINT(ctx->file_size) : NULL);
457 
458 			ctx->cached_set = GF_TRUE;
459 		}
460 
461 		if (ctx->range.num || ctx->range.den)
462 			gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_FILE_RANGE, &PROP_FRAC64(ctx->range) );
463 	}
464 
465 	//GFIO wrapper, gets stats and update
466 	if (!ctx->cached_set) {
467 		u64 bdone, btotal;
468 		Bool fcached;
469 		u32 bytes_per_sec;
470 		if (gf_fileio_get_stats((GF_FileIO *)ctx->file, &bdone, &btotal, &fcached, &bytes_per_sec)) {
471 			if (fcached) {
472 				gf_filter_pid_set_property(ctx->pid, GF_PROP_PID_FILE_CACHED, &PROP_BOOL(fcached) );
473 				ctx->cached_set = GF_TRUE;
474 			}
475 
476 			gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_SIZE, &PROP_LONGUINT(btotal) );
477 			gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT(bdone) );
478 			gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_RATE, &PROP_UINT(bytes_per_sec*8) );
479 		}
480 	}
481 
482 	pck = gf_filter_pck_new_shared(ctx->pid, ctx->block, nb_read, filein_pck_destructor);
483 	if (!pck)
484 		return GF_OK;
485 
486 	gf_filter_pck_set_byte_offset(pck, ctx->file_pos);
487 
488 	if (ctx->file_size && (ctx->file_pos + nb_read == ctx->file_size)) {
489 		ctx->is_end = GF_TRUE;
490 		gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT(ctx->file_size) );
491 	} else {
492 		if (nb_read < to_read) {
493 			Bool is_eof;
494 			GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[FileIn] Asked to read %d but got only %d\n", to_read, nb_read));
495 
496 			is_eof = gf_feof(ctx->file);
497 
498 			if (is_eof) {
499 				gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT(ctx->file_size) );
500 				ctx->is_end = GF_TRUE;
501 				GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileIn] IO error EOF found after reading %d bytes but file %s size is %d\n", ctx->file_pos+nb_read, ctx->src, ctx->file_size));
502 			}
503 		} else {
504 			gf_filter_pid_set_info(ctx->pid, GF_PROP_PID_DOWN_BYTES, &PROP_LONGUINT(ctx->file_pos) );
505 		}
506 	}
507 	gf_filter_pck_set_framing(pck, ctx->file_pos ? GF_FALSE : GF_TRUE, ctx->is_end);
508 	gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1);
509 	ctx->file_pos += nb_read;
510 
511 	ctx->pck_out = GF_TRUE;
512 	gf_filter_pck_send(pck);
513 
514 	if (ctx->file_size && gf_filter_reporting_enabled(filter)) {
515 		char szStatus[1024], *szSrc;
516 		szSrc = gf_file_basename(ctx->src);
517 
518 		sprintf(szStatus, "%s: % 16"LLD_SUF" /% 16"LLD_SUF" (%02.02f)", szSrc, (s64) ctx->file_pos, (s64) ctx->file_size, ((Double)ctx->file_pos*100.0)/ctx->file_size);
519 		gf_filter_update_status(filter, (u32) (ctx->file_pos*10000/ctx->file_size), szStatus);
520 	}
521 
522 	if (ctx->is_end) {
523 		gf_filter_pid_set_eos(ctx->pid);
524 		return GF_EOS;
525 	}
526 	return ctx->pck_out ? GF_EOS : GF_OK;
527 }
528 
529 
530 
531 #define OFFS(_n)	#_n, offsetof(GF_FileInCtx, _n)
532 
533 static const GF_FilterArgs FileInArgs[] =
534 {
535 	{ OFFS(src), "location of source content", GF_PROP_NAME, NULL, NULL, 0},
536 	{ OFFS(block_size), "block size used to read file. 0 means 5000 if file less than 500m, 1M otherwise", GF_PROP_UINT, "0", NULL, GF_FS_ARG_HINT_ADVANCED},
537 	{ OFFS(range), "byte range", GF_PROP_FRACTION64, "0-0", NULL, 0},
538 	{ OFFS(ext), "override file extension", GF_PROP_NAME, NULL, NULL, 0},
539 	{ OFFS(mime), "set file mime type", GF_PROP_NAME, NULL, NULL, 0},
540 	{0}
541 };
542 
543 static const GF_FilterCapability FileInCaps[] =
544 {
545 	CAP_UINT(GF_CAPS_OUTPUT,  GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
546 };
547 
548 GF_FilterRegister FileInRegister = {
549 	.name = "fin",
550 	GF_FS_SET_DESCRIPTION("File input")
551 	GF_FS_SET_HELP("This filter dispatch raw blocks from input file into a filter chain.\n"
552 	"Block size can be adjusted using [-block_size]().\n"
553 	"Content format can be forced through [-mime]() and file extension can be changed through [-ext]().\n"
554 	"Note: Unless disabled at session level (see [-no-probe](CORE) ), file extensions are usually ignored and format probing is done on the first data block.\n"
555 	"The special file name `null` is used for creating a file with no data, needed by some filters such as [dasher](dasher).\n"
556 	"The special file name `rand` is used to generate random data.\n"
557 	"The special file name `randsc` is used to generate random data with fake startcodes (0x000001).\n"
558 	"\n"
559 	"The filter handles both files and GF_FileIO objects as input URL.\n"
560 	)
561 	.private_size = sizeof(GF_FileInCtx),
562 	.args = FileInArgs,
563 	.initialize = filein_initialize,
564 	SETCAPS(FileInCaps),
565 	.finalize = filein_finalize,
566 	.process = filein_process,
567 	.process_event = filein_process_event,
568 	.probe_url = filein_probe_url
569 };
570 
571 
filein_register(GF_FilterSession * session)572 const GF_FilterRegister *filein_register(GF_FilterSession *session)
573 {
574 	return &FileInRegister;
575 }
576 
577