Line data Source code
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 output 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 : #include <gpac/xml.h>
30 :
31 : enum
32 : {
33 : FOUT_CAT_NONE = 0,
34 : FOUT_CAT_AUTO,
35 : FOUT_CAT_ALL
36 : };
37 :
38 : typedef struct
39 : {
40 : //options
41 : Double start, speed;
42 : char *dst, *mime, *ext;
43 : Bool append, dynext, ow, redund;
44 : u32 cat;
45 : u32 mvbk;
46 :
47 : //only one input pid
48 : GF_FilterPid *pid;
49 :
50 : FILE *file;
51 : Bool is_std;
52 : u64 nb_write;
53 : Bool use_templates;
54 : GF_FilterCapability in_caps[2];
55 : char szExt[10];
56 : char szFileName[GF_MAX_PATH];
57 :
58 : Bool patch_blocks;
59 : Bool is_null;
60 : GF_Err is_error;
61 : u32 dash_mode;
62 : u64 offset_at_seg_start;
63 : const char *original_url;
64 : GF_FileIO *gfio_ref;
65 :
66 : FILE *hls_chunk;
67 : } GF_FileOutCtx;
68 :
69 : #ifdef WIN32
70 : #include <io.h>
71 : #include <fcntl.h>
72 : #endif //WIN32
73 :
74 : static void fileout_close_hls_chunk(GF_FileOutCtx *ctx, Bool final_flush)
75 : {
76 7199 : if (!ctx->hls_chunk) return;
77 4 : gf_fclose(ctx->hls_chunk);
78 4 : ctx->hls_chunk = NULL;
79 : }
80 :
81 12537 : static GF_Err fileout_open_close(GF_FileOutCtx *ctx, const char *filename, const char *ext, u32 file_idx, Bool explicit_overwrite, char *file_suffix)
82 : {
83 12537 : if (ctx->file && !ctx->is_std) {
84 4976 : GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[FileOut] closing output file %s\n", ctx->szFileName));
85 4976 : gf_fclose(ctx->file);
86 :
87 : fileout_close_hls_chunk(ctx, GF_FALSE);
88 : }
89 12537 : ctx->file = NULL;
90 :
91 12537 : if (!filename)
92 : return GF_OK;
93 :
94 4978 : if (!strcmp(filename, "std")) ctx->is_std = GF_TRUE;
95 4978 : else if (!strcmp(filename, "stdout")) ctx->is_std = GF_TRUE;
96 4976 : else ctx->is_std = GF_FALSE;
97 :
98 4978 : if (ctx->is_std) {
99 2 : ctx->file = stdout;
100 : #ifdef WIN32
101 : _setmode(_fileno(stdout), _O_BINARY);
102 : #endif
103 :
104 : } else {
105 : char szFinalName[GF_MAX_PATH];
106 4976 : Bool append = ctx->append;
107 : const char *url = filename;
108 :
109 4976 : if (!strncmp(filename, "gfio://", 7))
110 5 : url = gf_fileio_translate_url(filename);
111 :
112 4976 : if (ctx->dynext) {
113 272 : const char *has_ext = gf_file_ext_start(url);
114 :
115 : strcpy(szFinalName, url);
116 272 : if (!has_ext && ext) {
117 : strcat(szFinalName, ".");
118 : strcat(szFinalName, ext);
119 : }
120 : } else {
121 : strcpy(szFinalName, url);
122 : }
123 :
124 4976 : if (ctx->use_templates) {
125 : GF_Err e;
126 : char szName[GF_MAX_PATH];
127 : assert(ctx->dst);
128 364 : if (!strcmp(filename, ctx->dst)) {
129 : strcpy(szName, szFinalName);
130 364 : e = gf_filter_pid_resolve_file_template(ctx->pid, szName, szFinalName, file_idx, file_suffix);
131 : } else {
132 : char szFileName[GF_MAX_PATH];
133 : strcpy(szFileName, szFinalName);
134 : strcpy(szName, ctx->dst);
135 0 : e = gf_filter_pid_resolve_file_template_ex(ctx->pid, szName, szFinalName, file_idx, file_suffix, szFileName);
136 : }
137 364 : if (e) {
138 0 : return ctx->is_error = e;
139 : }
140 : }
141 :
142 4976 : if (!gf_file_exists(szFinalName)) append = GF_FALSE;
143 :
144 4976 : if (!strcmp(szFinalName, ctx->szFileName) && (ctx->cat==FOUT_CAT_AUTO))
145 : append = GF_TRUE;
146 :
147 4976 : if (!ctx->ow && gf_file_exists(szFinalName) && !append) {
148 : char szRes[21];
149 : s32 res;
150 :
151 0 : fprintf(stderr, "File %s already exist - override (y/n/a) ?:", szFinalName);
152 0 : res = scanf("%20s", szRes);
153 0 : if (!res || (szRes[0] == 'n') || (szRes[0] == 'N')) {
154 0 : return ctx->is_error = GF_IO_ERR;
155 : }
156 0 : if ((szRes[0] == 'a') || (szRes[0] == 'A')) ctx->ow = GF_TRUE;
157 : }
158 :
159 4976 : GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[FileOut] opening output file %s\n", szFinalName));
160 4976 : ctx->file = gf_fopen_ex(szFinalName, ctx->original_url, append ? "a+b" : "w+b");
161 :
162 4976 : if (!strcmp(szFinalName, ctx->szFileName) && !append && ctx->nb_write && !explicit_overwrite) {
163 47 : GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[FileOut] re-opening in write mode output file %s, content overwrite (use `cat` option to enable append)\n", szFinalName));
164 : }
165 : strcpy(ctx->szFileName, szFinalName);
166 : }
167 4978 : ctx->nb_write = 0;
168 4978 : if (!ctx->file) {
169 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] cannot open output file %s\n", ctx->szFileName));
170 0 : return ctx->is_error = GF_IO_ERR;;
171 : }
172 :
173 : return GF_OK;
174 : }
175 :
176 1957 : static void fileout_setup_file(GF_FileOutCtx *ctx, Bool explicit_overwrite)
177 : {
178 1957 : const char *dst = ctx->dst;
179 : const GF_PropertyValue *p, *ext;
180 1957 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_OUTPATH);
181 1957 : ext = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_FILE_EXT);
182 :
183 1957 : if (p && p->value.string) {
184 13 : fileout_open_close(ctx, p->value.string, (ext && ctx->dynext) ? ext->value.string : NULL, 0, explicit_overwrite, NULL);
185 13 : return;
186 : }
187 1944 : if (!dst) {
188 0 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_FILEPATH);
189 0 : if (p && p->value.string) {
190 : dst = p->value.string;
191 0 : char *sep = strstr(dst, "://");
192 0 : if (sep) {
193 0 : dst = strchr(sep+3, '/');
194 0 : if (!dst) return;
195 : } else {
196 0 : if (!strncmp(dst, "./", 2)) dst+= 2;
197 0 : else if (!strncmp(dst, ".\\", 2)) dst+= 2;
198 0 : else if (!strncmp(dst, "../", 3)) dst+= 3;
199 0 : else if (!strncmp(dst, "..\\", 3)) dst+= 3;
200 : }
201 : }
202 : }
203 1944 : if (ctx->dynext) {
204 30 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PCK_FILENUM);
205 30 : if (!p) {
206 30 : if (ext && ext->value.string) {
207 30 : fileout_open_close(ctx, dst, ext->value.string, 0, explicit_overwrite, NULL);
208 : }
209 : }
210 1914 : } else if (ctx->dst) {
211 1914 : fileout_open_close(ctx, ctx->dst, NULL, 0, explicit_overwrite, NULL);
212 : } else {
213 0 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_FILEPATH);
214 0 : if (!p) p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_URL);
215 0 : if (p && p->value.string)
216 0 : fileout_open_close(ctx, p->value.string, NULL, 0, explicit_overwrite, NULL);
217 : }
218 : }
219 2886 : static GF_Err fileout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
220 : {
221 : const GF_PropertyValue *p;
222 2886 : GF_FileOutCtx *ctx = (GF_FileOutCtx *) gf_filter_get_udta(filter);
223 2886 : if (is_remove) {
224 10 : ctx->pid = NULL;
225 10 : fileout_open_close(ctx, NULL, NULL, 0, GF_FALSE, NULL);
226 10 : return GF_OK;
227 : }
228 2876 : gf_filter_pid_check_caps(pid);
229 :
230 2876 : if (!ctx->pid) {
231 : GF_FilterEvent evt;
232 1946 : gf_filter_pid_init_play_event(pid, &evt, ctx->start, ctx->speed, "FileOut");
233 1946 : gf_filter_pid_send_event(pid, &evt);
234 : }
235 2876 : ctx->pid = pid;
236 :
237 2876 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_DISABLE_PROGRESSIVE);
238 2876 : if (p && p->value.uint) ctx->patch_blocks = GF_TRUE;
239 :
240 2876 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_DASH_MODE);
241 2876 : if (p && p->value.uint) ctx->dash_mode = 1;
242 : return GF_OK;
243 : }
244 :
245 2223 : static GF_Err fileout_initialize(GF_Filter *filter)
246 : {
247 : char *ext=NULL, *sep;
248 : const char *dst;
249 2223 : GF_FileOutCtx *ctx = (GF_FileOutCtx *) gf_filter_get_udta(filter);
250 :
251 2223 : if (!ctx || !ctx->dst) return GF_OK;
252 :
253 2223 : if (!ctx->mvbk)
254 0 : ctx->mvbk = 1;
255 :
256 2223 : if (strnicmp(ctx->dst, "file:/", 6) && strnicmp(ctx->dst, "gfio:/", 6) && strstr(ctx->dst, "://")) {
257 0 : gf_filter_setup_failure(filter, GF_NOT_SUPPORTED);
258 0 : return GF_NOT_SUPPORTED;
259 : }
260 2223 : if (!stricmp(ctx->dst, "null") ) {
261 3 : ctx->is_null = GF_TRUE;
262 : //null and no format specified, we accept any kind
263 3 : if (!ctx->ext) {
264 0 : ctx->in_caps[0].code = GF_PROP_PID_STREAM_TYPE;
265 0 : ctx->in_caps[0].val = PROP_UINT(GF_STREAM_UNKNOWN);
266 0 : ctx->in_caps[0].flags = GF_CAPS_INPUT_EXCLUDED;
267 0 : gf_filter_override_caps(filter, ctx->in_caps, 1);
268 0 : return GF_OK;
269 : }
270 : }
271 2223 : if (!strncmp(ctx->dst, "gfio://", 7)) {
272 : GF_Err e;
273 3 : ctx->gfio_ref = gf_fileio_open_url(gf_fileio_from_url(ctx->dst), NULL, "ref", &e);
274 3 : if (!ctx->gfio_ref) {
275 0 : gf_filter_setup_failure(filter, e);
276 0 : return e;
277 : }
278 3 : dst = gf_fileio_translate_url(ctx->dst);
279 3 : ctx->original_url = ctx->dst;
280 : } else {
281 : dst = ctx->dst;
282 : }
283 :
284 2223 : sep = dst ? strchr(dst, '$') : NULL;
285 2223 : if (sep) {
286 80 : sep = strchr(sep+1, '$');
287 80 : if (sep) ctx->use_templates = GF_TRUE;
288 : }
289 :
290 2223 : if (ctx->dynext) return GF_OK;
291 :
292 2166 : if (ctx->ext) ext = ctx->ext;
293 2156 : else if (dst) {
294 2156 : ext = gf_file_ext_start(dst);
295 2156 : if (!ext) ext = ".*";
296 2156 : ext += 1;
297 : }
298 :
299 2166 : if (!ext && !ctx->mime) {
300 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] No extension provided nor mime type for output file %s, cannot infer format\n", ctx->dst));
301 : return GF_NOT_SUPPORTED;
302 : }
303 : //static cap, streamtype = file
304 2166 : ctx->in_caps[0].code = GF_PROP_PID_STREAM_TYPE;
305 2166 : ctx->in_caps[0].val = PROP_UINT(GF_STREAM_FILE);
306 2166 : ctx->in_caps[0].flags = GF_CAPS_INPUT_STATIC;
307 :
308 2166 : if (ctx->mime) {
309 318 : ctx->in_caps[1].code = GF_PROP_PID_MIME;
310 318 : ctx->in_caps[1].val = PROP_NAME( ctx->mime );
311 318 : ctx->in_caps[1].flags = GF_CAPS_INPUT;
312 : } else {
313 1848 : strncpy(ctx->szExt, ext, 9);
314 1848 : ctx->szExt[9] = 0;
315 1848 : strlwr(ctx->szExt);
316 1848 : ctx->in_caps[1].code = GF_PROP_PID_FILE_EXT;
317 1848 : ctx->in_caps[1].val = PROP_NAME( ctx->szExt );
318 1848 : ctx->in_caps[1].flags = GF_CAPS_INPUT;
319 : }
320 2166 : gf_filter_override_caps(filter, ctx->in_caps, 2);
321 :
322 2166 : return GF_OK;
323 : }
324 :
325 2223 : static void fileout_finalize(GF_Filter *filter)
326 : {
327 : GF_Err e;
328 2223 : GF_FileOutCtx *ctx = (GF_FileOutCtx *) gf_filter_get_udta(filter);
329 :
330 : fileout_close_hls_chunk(ctx, GF_TRUE);
331 :
332 2223 : fileout_open_close(ctx, NULL, NULL, 0, GF_FALSE, NULL);
333 2223 : if (ctx->gfio_ref)
334 3 : gf_fileio_open_url((GF_FileIO *)ctx->gfio_ref, NULL, "unref", &e);
335 2223 : }
336 :
337 176699 : static GF_Err fileout_process(GF_Filter *filter)
338 : {
339 : GF_FilterPacket *pck;
340 : const GF_PropertyValue *fname, *p;
341 : Bool start, end;
342 : const u8 *pck_data;
343 : u32 pck_size, nb_write;
344 176699 : GF_FileOutCtx *ctx = (GF_FileOutCtx *) gf_filter_get_udta(filter);
345 :
346 176699 : if (ctx->is_error) {
347 : GF_Err e = ctx->is_error;
348 0 : if (e != GF_EOS) {
349 0 : gf_filter_pid_set_discard(ctx->pid, GF_TRUE);
350 0 : ctx->is_error = GF_EOS;
351 : }
352 : return e;
353 : }
354 :
355 176699 : pck = gf_filter_pid_get_packet(ctx->pid);
356 176699 : if (!pck) {
357 19774 : if (gf_filter_pid_is_eos(ctx->pid)) {
358 1869 : if (gf_filter_reporting_enabled(filter)) {
359 : char szStatus[1024];
360 0 : snprintf(szStatus, 1024, "%s: done - wrote "LLU" bytes", gf_file_basename(ctx->szFileName), ctx->nb_write);
361 0 : gf_filter_update_status(filter, 10000, szStatus);
362 : }
363 :
364 1869 : if (ctx->dash_mode && ctx->file) {
365 : GF_FilterEvent evt;
366 6 : GF_FEVT_INIT(evt, GF_FEVT_SEGMENT_SIZE, ctx->pid);
367 : evt.seg_size.seg_url = NULL;
368 :
369 6 : if (ctx->dash_mode==1) {
370 0 : evt.seg_size.is_init = GF_TRUE;
371 0 : ctx->dash_mode = 2;
372 : evt.seg_size.media_range_start = 0;
373 : evt.seg_size.media_range_end = 0;
374 0 : gf_filter_pid_send_event(ctx->pid, &evt);
375 : } else {
376 : evt.seg_size.is_init = GF_FALSE;
377 6 : evt.seg_size.media_range_start = ctx->offset_at_seg_start;
378 6 : evt.seg_size.media_range_end = gf_ftell(ctx->file)-1;
379 6 : gf_filter_pid_send_event(ctx->pid, &evt);
380 : }
381 : }
382 1869 : fileout_open_close(ctx, NULL, NULL, 0, GF_FALSE, NULL);
383 1869 : return GF_EOS;
384 : }
385 : return GF_OK;
386 : }
387 :
388 156925 : gf_filter_pck_get_framing(pck, &start, &end);
389 156925 : if (!ctx->redund) {
390 156925 : u32 dep_flags = gf_filter_pck_get_dependency_flags(pck);
391 : //redundant packet, do not store
392 156925 : if ((dep_flags & 0x3) == 1) {
393 1894 : gf_filter_pid_drop_packet(ctx->pid);
394 1894 : return GF_OK;
395 : }
396 : }
397 :
398 155031 : if (ctx->is_null) {
399 868 : if (start) {
400 : u32 fnum=0;
401 : const char *filename=NULL;
402 3 : p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM);
403 3 : if (p) fnum = p->value.uint;
404 3 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_URL);
405 3 : if (!p) p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_OUTPATH);
406 3 : if (!p) p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME);
407 3 : if (p) filename = p->value.string;
408 3 : if (filename) {
409 3 : strcpy(ctx->szFileName, filename);
410 : } else {
411 0 : sprintf(ctx->szFileName, "%d", fnum);
412 : }
413 3 : GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[FileOut] null open (file name is %s)\n", ctx->szFileName));
414 : }
415 868 : if (end) {
416 0 : GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[FileOut] null close (file name was %s)\n", ctx->szFileName));
417 : }
418 868 : gf_filter_pid_drop_packet(ctx->pid);
419 868 : return fileout_process(filter);
420 : }
421 :
422 154163 : if (ctx->file && start && (ctx->cat==FOUT_CAT_ALL))
423 0 : start = GF_FALSE;
424 :
425 154163 : if (ctx->dash_mode) {
426 1645 : p = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM);
427 1645 : if (p) {
428 : GF_FilterEvent evt;
429 :
430 66 : GF_FEVT_INIT(evt, GF_FEVT_SEGMENT_SIZE, ctx->pid);
431 : evt.seg_size.seg_url = NULL;
432 :
433 66 : if (ctx->dash_mode==1) {
434 6 : evt.seg_size.is_init = GF_TRUE;
435 6 : ctx->dash_mode = 2;
436 : evt.seg_size.media_range_start = 0;
437 : evt.seg_size.media_range_end = 0;
438 6 : gf_filter_pid_send_event(ctx->pid, &evt);
439 : } else {
440 : evt.seg_size.is_init = GF_FALSE;
441 60 : evt.seg_size.media_range_start = ctx->offset_at_seg_start;
442 60 : evt.seg_size.media_range_end = gf_ftell(ctx->file)-1;
443 60 : ctx->offset_at_seg_start = evt.seg_size.media_range_end+1;
444 60 : gf_filter_pid_send_event(ctx->pid, &evt);
445 : }
446 66 : if ( gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME))
447 22 : start = GF_TRUE;
448 : }
449 : }
450 :
451 154163 : if (start) {
452 : const GF_PropertyValue *ext, *fnum, *fsuf;
453 : Bool explicit_overwrite = GF_FALSE;
454 : const char *name = NULL;
455 : fname = ext = NULL;
456 : //file num increased per packet, open new file
457 4979 : fnum = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENUM);
458 4979 : if (fnum) {
459 2883 : fname = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_OUTPATH);
460 2883 : ext = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_FILE_EXT);
461 2883 : if (!fname) name = ctx->dst;
462 : }
463 : //filename change at packet start, open new file
464 4979 : if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILENAME);
465 4979 : if (!fname) fname = gf_filter_pck_get_property(pck, GF_PROP_PID_OUTPATH);
466 4979 : if (!ext) ext = gf_filter_pck_get_property(pck, GF_PROP_PID_FILE_EXT);
467 4979 : if (fname) name = fname->value.string;
468 :
469 4979 : fsuf = gf_filter_pck_get_property(pck, GF_PROP_PCK_FILESUF);
470 :
471 4979 : if (end && gf_filter_pck_get_seek_flag(pck))
472 : explicit_overwrite = GF_TRUE;
473 :
474 4979 : if (name) {
475 3021 : fileout_open_close(ctx, name, ext ? ext->value.string : NULL, fnum ? fnum->value.uint : 0, explicit_overwrite, fsuf ? fsuf->value.string : NULL);
476 1958 : } else if (!ctx->file) {
477 1957 : fileout_setup_file(ctx, explicit_overwrite);
478 : }
479 : }
480 :
481 154163 : p = gf_filter_pck_get_property(pck, GF_PROP_PCK_HLS_FRAG_NUM);
482 154163 : if (p) {
483 : char szHLSChunk[GF_MAX_PATH+21];
484 40 : snprintf(szHLSChunk, GF_MAX_PATH+20, "%s.%d", ctx->szFileName, p->value.uint);
485 40 : if (ctx->hls_chunk) gf_fclose(ctx->hls_chunk);
486 40 : ctx->hls_chunk = gf_fopen_ex(szHLSChunk, ctx->original_url, "w+b");
487 : }
488 :
489 154163 : pck_data = gf_filter_pck_get_data(pck, &pck_size);
490 154163 : if (ctx->file) {
491 154163 : GF_FilterFrameInterface *hwf = gf_filter_pck_get_frame_interface(pck);
492 154163 : if (pck_data) {
493 146883 : if (ctx->patch_blocks && gf_filter_pck_get_seek_flag(pck)) {
494 4 : u64 bo = gf_filter_pck_get_byte_offset(pck);
495 4 : if (ctx->is_std) {
496 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Cannot patch file, output is stdout\n"));
497 4 : } else if (bo==GF_FILTER_NO_BO) {
498 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Cannot patch file, wrong byte offset\n"));
499 : } else {
500 4 : u32 ilaced = gf_filter_pck_get_interlaced(pck);
501 4 : u64 pos = ctx->nb_write;
502 4 : if (ctx->file) {
503 : assert( ctx->nb_write == gf_ftell(ctx->file) );
504 : }
505 :
506 : //we are inserting a block: write dummy bytes at end and move bytes
507 4 : if (ilaced) {
508 : u8 *block;
509 : u64 cur_r, cur_w;
510 1 : nb_write = (u32) gf_fwrite(pck_data, pck_size, ctx->file);
511 :
512 1 : if (nb_write!=pck_size) {
513 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Write error, wrote %d bytes but had %d to write\n", nb_write, pck_size));
514 : }
515 1 : cur_w = gf_ftell(ctx->file);
516 :
517 1 : gf_fseek(ctx->file, pos, SEEK_SET);
518 :
519 : cur_r = pos;
520 : pos = cur_w;
521 1 : block = gf_malloc(ctx->mvbk);
522 1 : if (!block) {
523 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] unable to allocate block of %d bytes\n", ctx->mvbk));
524 : } else {
525 17 : while (cur_r > bo) {
526 16 : u32 move_bytes = ctx->mvbk;
527 16 : if (cur_r - bo < move_bytes)
528 1 : move_bytes = (u32) (cur_r - bo);
529 :
530 16 : gf_fseek(ctx->file, cur_r - move_bytes, SEEK_SET);
531 16 : nb_write = (u32) gf_fread(block, (size_t) move_bytes, ctx->file);
532 :
533 16 : if (nb_write!=move_bytes) {
534 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Read error, got %d bytes but had %d to read\n", nb_write, move_bytes));
535 : }
536 :
537 16 : gf_fseek(ctx->file, cur_w - move_bytes, SEEK_SET);
538 16 : nb_write = (u32) gf_fwrite(block, (size_t) move_bytes, ctx->file);
539 :
540 16 : if (nb_write!=move_bytes) {
541 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Write error, wrote %d bytes but had %d to write\n", nb_write, move_bytes));
542 : }
543 : cur_r -= move_bytes;
544 : cur_w -= move_bytes;
545 : }
546 1 : gf_free(block);
547 : }
548 : }
549 :
550 4 : gf_fseek(ctx->file, bo, SEEK_SET);
551 4 : nb_write = (u32) gf_fwrite(pck_data, pck_size, ctx->file);
552 4 : gf_fseek(ctx->file, pos, SEEK_SET);
553 :
554 4 : if (nb_write!=pck_size) {
555 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Write error, wrote %d bytes but had %d to write\n", nb_write, pck_size));
556 : }
557 : }
558 : } else {
559 146879 : nb_write = (u32) gf_fwrite(pck_data, pck_size, ctx->file);
560 146879 : if (nb_write!=pck_size) {
561 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Write error, wrote %d bytes but had %d to write\n", nb_write, pck_size));
562 : }
563 146879 : ctx->nb_write += nb_write;
564 :
565 146879 : if (ctx->hls_chunk) {
566 80 : nb_write = (u32) gf_fwrite(pck_data, pck_size, ctx->hls_chunk);
567 80 : if (nb_write!=pck_size) {
568 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Write error, wrote %d bytes but had %d to write\n", nb_write, pck_size));
569 : }
570 : }
571 : }
572 7280 : } else if (hwf) {
573 : u32 w, h, stride, stride_uv, pf;
574 : u32 nb_planes, uv_height;
575 7280 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_WIDTH);
576 7280 : w = p ? p->value.uint : 0;
577 7280 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_HEIGHT);
578 7280 : h = p ? p->value.uint : 0;
579 7280 : p = gf_filter_pid_get_property(ctx->pid, GF_PROP_PID_PIXFMT);
580 7280 : pf = p ? p->value.uint : 0;
581 :
582 7280 : stride = stride_uv = 0;
583 :
584 7280 : if (gf_pixel_get_size_info(pf, w, h, NULL, &stride, &stride_uv, &nb_planes, &uv_height) == GF_TRUE) {
585 : u32 i;
586 22596 : for (i=0; i<nb_planes; i++) {
587 : u32 j, write_h, lsize;
588 : const u8 *out_ptr;
589 7658 : u32 out_stride = i ? stride_uv : stride;
590 7658 : GF_Err e = hwf->get_plane(hwf, i, &out_ptr, &out_stride);
591 7658 : if (e) {
592 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Failed to fetch plane data from hardware frame, cannot write\n"));
593 0 : break;
594 : }
595 7658 : if (i) {
596 378 : write_h = uv_height;
597 378 : lsize = stride_uv;
598 : } else {
599 : write_h = h;
600 7280 : lsize = stride;
601 : }
602 1353578 : for (j=0; j<write_h; j++) {
603 1345920 : nb_write = (u32) gf_fwrite(out_ptr, lsize, ctx->file);
604 1345920 : if (nb_write!=lsize) {
605 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] Write error, wrote %d bytes but had %d to write\n", nb_write, lsize));
606 : }
607 1345920 : ctx->nb_write += nb_write;
608 1345920 : out_ptr += out_stride;
609 : }
610 : }
611 : }
612 : } else {
613 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[FileOut] No data associated with packet, cannot write\n"));
614 : }
615 : } else {
616 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[FileOut] output file handle is not opened, discarding %d bytes\n", pck_size));
617 : }
618 154163 : gf_filter_pid_drop_packet(ctx->pid);
619 154163 : if (end && !ctx->cat) {
620 3457 : fileout_open_close(ctx, NULL, NULL, 0, GF_FALSE, NULL);
621 : }
622 154163 : if (gf_filter_reporting_enabled(filter)) {
623 : char szStatus[1024];
624 0 : snprintf(szStatus, 1024, "%s: wrote % 16"LLD_SUF" bytes", gf_file_basename(ctx->szFileName), (s64) ctx->nb_write);
625 0 : gf_filter_update_status(filter, -1, szStatus);
626 : }
627 : return GF_OK;
628 : }
629 :
630 60135 : static Bool fileout_process_event(GF_Filter *filter, const GF_FilterEvent *evt)
631 : {
632 60135 : if (evt->base.type==GF_FEVT_FILE_DELETE) {
633 1 : GF_FileOutCtx *ctx = (GF_FileOutCtx *) gf_filter_get_udta(filter);
634 1 : if (ctx->is_null) {
635 0 : GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[FileOut] null delete (file name was %s)\n", evt->file_del.url));
636 : } else {
637 1 : gf_file_delete(evt->file_del.url);
638 : }
639 : return GF_TRUE;
640 : }
641 : return GF_FALSE;
642 : }
643 2793 : static GF_FilterProbeScore fileout_probe_url(const char *url, const char *mime)
644 : {
645 2793 : if (strstr(url, "://")) {
646 :
647 84 : if (!strnicmp(url, "file://", 7)) return GF_FPROBE_MAYBE_SUPPORTED;
648 84 : if (!strnicmp(url, "gfio://", 7)) {
649 6 : if (!gf_fileio_write_mode(gf_fileio_from_url(url)))
650 : return GF_FPROBE_NOT_SUPPORTED;
651 6 : return GF_FPROBE_MAYBE_SUPPORTED;
652 : }
653 : return GF_FPROBE_NOT_SUPPORTED;
654 : }
655 : return GF_FPROBE_MAYBE_SUPPORTED;
656 : }
657 :
658 :
659 : #define OFFS(_n) #_n, offsetof(GF_FileOutCtx, _n)
660 :
661 : static const GF_FilterArgs FileOutArgs[] =
662 : {
663 : { OFFS(dst), "location of destination file - see filter help ", GF_PROP_NAME, NULL, NULL, 0},
664 : { OFFS(append), "open in append mode", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
665 : { OFFS(dynext), "indicate the file extension is set by filter chain, not dst", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
666 : { OFFS(start), "set playback start offset. Negative value means percent of media duration with -1 equal to duration", GF_PROP_DOUBLE, "0.0", NULL, 0},
667 : { OFFS(speed), "set playback speed when vsync is on. If speed is negative and start is 0, start is set to -1", GF_PROP_DOUBLE, "1.0", NULL, 0},
668 : { OFFS(ext), "set extension for graph resolution, regardless of file extension", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_ADVANCED},
669 : { OFFS(mime), "set mime type for graph resolution", GF_PROP_NAME, NULL, NULL, GF_FS_ARG_HINT_EXPERT},
670 : { OFFS(cat), "cat each file of input pid rather than creating one file per filename\n"
671 : "- none: never cat files\n"
672 : "- auto: only cat if files have same names\n"
673 : "- all: always cat regardless of file names"
674 : , GF_PROP_UINT, "none", "none|auto|all", GF_FS_ARG_HINT_ADVANCED},
675 : { OFFS(ow), "overwrite output if existing", GF_PROP_BOOL, "true", NULL, 0},
676 : { OFFS(mvbk), "block size used when moving parts of the file around in patch mode", GF_PROP_UINT, "8192", NULL, 0},
677 : { OFFS(redund), "keep redundant packet in output file", GF_PROP_BOOL, "false", NULL, 0},
678 :
679 : {0}
680 : };
681 :
682 : static const GF_FilterCapability FileOutCaps[] =
683 : {
684 : CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
685 : CAP_STRING(GF_CAPS_INPUT,GF_PROP_PID_FILE_EXT, "*"),
686 : };
687 :
688 :
689 : GF_FilterRegister FileOutRegister = {
690 : .name = "fout",
691 : GF_FS_SET_DESCRIPTION("File output")
692 : GF_FS_SET_HELP("The file output filter is used to write output to disk, and does not produce any output PID.\n"
693 : "It can work as a null sink when its destination is `null`, dropping all input packets. In this case it accepts ANY type of input pid, not just file ones.\n"
694 : "In regular mode, the filter only accept pid of type file. It will dump to file incomming packets (stream type file), starting a new file for each packet having a __frame_start__ flag set, unless operating in [-cat]() mode.\n"
695 : "If the output file name is `std` or `stdout`, writes to stdout.\n"
696 : "The ouput file name can use gpac templating mechanism, see `gpac -h doc`."
697 : "The filter watches the property `FileNumber` on incoming packets to create new files.\n"
698 : )
699 : .private_size = sizeof(GF_FileOutCtx),
700 : .args = FileOutArgs,
701 : SETCAPS(FileOutCaps),
702 : .probe_url = fileout_probe_url,
703 : .initialize = fileout_initialize,
704 : .finalize = fileout_finalize,
705 : .configure_pid = fileout_configure_pid,
706 : .process = fileout_process,
707 : .process_event = fileout_process_event
708 : };
709 :
710 :
711 2877 : const GF_FilterRegister *fileout_register(GF_FilterSession *session)
712 : {
713 2877 : return &FileOutRegister;
714 : }
715 :
|