timecode.c 16.7 KB
Newer Older
Yusuke Nakamura's avatar
Yusuke Nakamura committed
1
/*****************************************************************************
2
 * timecode.c: timecode file input
Yusuke Nakamura's avatar
Yusuke Nakamura committed
3
 *****************************************************************************
Henrik Gramner's avatar
Henrik Gramner committed
4
 * Copyright (C) 2010-2016 x264 project
Yusuke Nakamura's avatar
Yusuke Nakamura committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 *
 * Authors: Yusuke Nakamura <muken.the.vfrmaniac@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111, USA.
21 22 23
 *
 * This program is also available under a commercial proprietary license.
 * For more information, contact us at licensing@x264.com.
Yusuke Nakamura's avatar
Yusuke Nakamura committed
24 25
 *****************************************************************************/

26 27
#include "input.h"
#define FAIL_IF_ERROR( cond, ... ) FAIL_IF_ERR( cond, "timecode", __VA_ARGS__ )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
28 29 30 31 32 33 34

typedef struct
{
    cli_input_t input;
    hnd_t p_handle;
    int auto_timebase_num;
    int auto_timebase_den;
35 36
    uint64_t timebase_num;
    uint64_t timebase_den;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
37 38 39 40 41 42
    int stored_pts_num;
    int64_t *pts;
    double assume_fps;
    double last_timecode;
} timecode_hnd_t;

43
static inline double sigexp10( double value, double *exponent )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
44 45
{
    /* This function separates significand and exp10 from double floating point. */
46 47
    *exponent = pow( 10, floor( log10( value ) ) );
    return value / *exponent;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
48 49 50 51 52 53 54
}

#define DOUBLE_EPSILON 5e-6
#define MKV_TIMEBASE_DEN 1000000000

static double correct_fps( double fps, timecode_hnd_t *h )
{
55 56
    int i = 1;
    uint64_t fps_num, fps_den;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
57 58 59 60 61 62
    double exponent;
    double fps_sig = sigexp10( fps, &exponent );
    while( 1 )
    {
        fps_den = i * h->timebase_num;
        fps_num = round( fps_den * fps_sig ) * exponent;
63
        FAIL_IF_ERROR( fps_num > UINT32_MAX, "tcfile fps correction failed.\n"
64
                       "                  Specify an appropriate timebase manually or remake tcfile.\n" );
Yusuke Nakamura's avatar
Yusuke Nakamura committed
65 66 67 68 69 70 71
        if( fabs( ((double)fps_num / fps_den) / exponent - fps_sig ) < DOUBLE_EPSILON )
            break;
        ++i;
    }
    if( h->auto_timebase_den )
    {
        h->timebase_den = h->timebase_den ? lcm( h->timebase_den, fps_num ) : fps_num;
72
        if( h->timebase_den > UINT32_MAX )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
73 74 75 76 77 78 79 80 81
            h->auto_timebase_den = 0;
    }
    return (double)fps_num / fps_den;
}

static int try_mkv_timebase_den( double *fpss, timecode_hnd_t *h, int loop_num )
{
    h->timebase_num = 0;
    h->timebase_den = MKV_TIMEBASE_DEN;
82
    for( int num = 0; num < loop_num; num++ )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
83
    {
84
        uint64_t fps_den;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
85 86 87
        double exponent;
        double fps_sig = sigexp10( fpss[num], &exponent );
        fps_den = round( MKV_TIMEBASE_DEN / fps_sig ) / exponent;
88
        h->timebase_num = fps_den && h->timebase_num ? gcd( h->timebase_num, fps_den ) : fps_den;
89
        FAIL_IF_ERROR( h->timebase_num > UINT32_MAX || !h->timebase_num, "automatic timebase generation failed.\n"
90
                       "                  Specify timebase manually.\n" );
Yusuke Nakamura's avatar
Yusuke Nakamura committed
91 92 93 94 95 96 97 98 99 100 101 102
    }
    return 0;
}

static int parse_tcfile( FILE *tcfile_in, timecode_hnd_t *h, video_info_t *info )
{
    char buff[256];
    int ret, tcfv, num, seq_num, timecodes_num;
    double *timecodes = NULL;
    double *fpss = NULL;

    ret = fscanf( tcfile_in, "# timecode format v%d", &tcfv );
103
    FAIL_IF_ERROR( ret != 1 || (tcfv != 1 && tcfv != 2), "unsupported timecode format\n" );
104
#define NO_TIMECODE_LINE (buff[0] == '#' || buff[0] == '\n' || buff[0] == '\r')
Yusuke Nakamura's avatar
Yusuke Nakamura committed
105 106 107 108
    if( tcfv == 1 )
    {
        uint64_t file_pos;
        double assume_fps, seq_fps;
109
        int start, end = -1;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
110 111 112 113 114
        int prev_start = -1, prev_end = -1;

        h->assume_fps = 0;
        for( num = 2; fgets( buff, sizeof(buff), tcfile_in ) != NULL; num++ )
        {
115
            if( NO_TIMECODE_LINE )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
116
                continue;
117
            FAIL_IF_ERROR( sscanf( buff, "assume %lf", &h->assume_fps ) != 1 && sscanf( buff, "Assume %lf", &h->assume_fps ) != 1,
118
                           "tcfile parsing error: assumed fps not found\n" );
Yusuke Nakamura's avatar
Yusuke Nakamura committed
119 120
            break;
        }
121
        FAIL_IF_ERROR( h->assume_fps <= 0, "invalid assumed fps %.6f\n", h->assume_fps );
Yusuke Nakamura's avatar
Yusuke Nakamura committed
122 123 124 125 126

        file_pos = ftell( tcfile_in );
        h->stored_pts_num = 0;
        for( seq_num = 0; fgets( buff, sizeof(buff), tcfile_in ) != NULL; num++ )
        {
127
            if( NO_TIMECODE_LINE )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
128 129
            {
                if( sscanf( buff, "# TDecimate Mode 3:  Last Frame = %d", &end ) == 1 )
130
                    h->stored_pts_num = end + 1;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
131 132 133
                continue;
            }
            ret = sscanf( buff, "%d,%d,%lf", &start, &end, &seq_fps );
134
            FAIL_IF_ERROR( ret != 3 && ret != EOF, "invalid input tcfile\n" );
135
            FAIL_IF_ERROR( start > end || start <= prev_start || end <= prev_end || seq_fps <= 0,
136
                           "invalid input tcfile at line %d: %s\n", num, buff );
Yusuke Nakamura's avatar
Yusuke Nakamura committed
137 138 139 140 141 142
            prev_start = start;
            prev_end = end;
            if( h->auto_timebase_den || h->auto_timebase_num )
                ++seq_num;
        }
        if( !h->stored_pts_num )
143
            h->stored_pts_num = end + 2;
144
        timecodes_num = h->stored_pts_num;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
        fseek( tcfile_in, file_pos, SEEK_SET );

        timecodes = malloc( timecodes_num * sizeof(double) );
        if( !timecodes )
            return -1;
        if( h->auto_timebase_den || h->auto_timebase_num )
        {
            fpss = malloc( (seq_num + 1) * sizeof(double) );
            if( !fpss )
                goto fail;
        }

        assume_fps = correct_fps( h->assume_fps, h );
        if( assume_fps < 0 )
            goto fail;
        timecodes[0] = 0;
161
        for( num = seq_num = 0; num < timecodes_num - 1 && fgets( buff, sizeof(buff), tcfile_in ) != NULL; )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
162
        {
163
            if( NO_TIMECODE_LINE )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
164 165 166 167 168 169
                continue;
            ret = sscanf( buff, "%d,%d,%lf", &start, &end, &seq_fps );
            if( ret != 3 )
                start = end = timecodes_num - 1;
            for( ; num < start && num < timecodes_num - 1; num++ )
                timecodes[num + 1] = timecodes[num] + 1 / assume_fps;
170 171 172 173 174 175 176 177 178 179
            if( num < timecodes_num - 1 )
            {
                if( h->auto_timebase_den || h->auto_timebase_num )
                    fpss[seq_num++] = seq_fps;
                seq_fps = correct_fps( seq_fps, h );
                if( seq_fps < 0 )
                    goto fail;
                for( num = start; num <= end && num < timecodes_num - 1; num++ )
                    timecodes[num + 1] = timecodes[num] + 1 / seq_fps;
            }
Yusuke Nakamura's avatar
Yusuke Nakamura committed
180
        }
181 182
        for( ; num < timecodes_num - 1; num++ )
            timecodes[num + 1] = timecodes[num] + 1 / assume_fps;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
183 184 185 186 187 188 189 190 191 192 193 194
        if( h->auto_timebase_den || h->auto_timebase_num )
            fpss[seq_num] = h->assume_fps;

        if( h->auto_timebase_num && !h->auto_timebase_den )
        {
            double exponent;
            double assume_fps_sig, seq_fps_sig;
            if( try_mkv_timebase_den( fpss, h, seq_num + 1 ) < 0 )
                goto fail;
            fseek( tcfile_in, file_pos, SEEK_SET );
            assume_fps_sig = sigexp10( h->assume_fps, &exponent );
            assume_fps = MKV_TIMEBASE_DEN / ( round( MKV_TIMEBASE_DEN / assume_fps_sig ) / exponent );
195
            for( num = 0; num < timecodes_num - 1 && fgets( buff, sizeof(buff), tcfile_in ) != NULL; )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
196
            {
197
                if( NO_TIMECODE_LINE )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
198 199 200 201 202 203
                    continue;
                ret = sscanf( buff, "%d,%d,%lf", &start, &end, &seq_fps );
                if( ret != 3 )
                    start = end = timecodes_num - 1;
                seq_fps_sig = sigexp10( seq_fps, &exponent );
                seq_fps = MKV_TIMEBASE_DEN / ( round( MKV_TIMEBASE_DEN / seq_fps_sig ) / exponent );
204
                for( ; num < start && num < timecodes_num - 1; num++ )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
205 206 207 208
                    timecodes[num + 1] = timecodes[num] + 1 / assume_fps;
                for( num = start; num <= end && num < timecodes_num - 1; num++ )
                    timecodes[num + 1] = timecodes[num] + 1 / seq_fps;
            }
209 210
            for( ; num < timecodes_num - 1; num++ )
                timecodes[num + 1] = timecodes[num] + 1 / assume_fps;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
211 212
        }
        if( fpss )
213
        {
Yusuke Nakamura's avatar
Yusuke Nakamura committed
214
            free( fpss );
215 216
            fpss = NULL;
        }
Yusuke Nakamura's avatar
Yusuke Nakamura committed
217 218 219 220 221 222 223 224

        h->assume_fps = assume_fps;
        h->last_timecode = timecodes[timecodes_num - 1];
    }
    else    /* tcfv == 2 */
    {
        uint64_t file_pos = ftell( tcfile_in );

225
        h->stored_pts_num = 0;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
226 227
        while( fgets( buff, sizeof(buff), tcfile_in ) != NULL )
        {
228
            if( NO_TIMECODE_LINE )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
229
            {
230
                if( !h->stored_pts_num )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
231 232 233
                    file_pos = ftell( tcfile_in );
                continue;
            }
234
            h->stored_pts_num++;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
235
        }
236
        timecodes_num = h->stored_pts_num;
237
        FAIL_IF_ERROR( !timecodes_num, "input tcfile doesn't have any timecodes!\n" );
Yusuke Nakamura's avatar
Yusuke Nakamura committed
238 239 240 241 242 243
        fseek( tcfile_in, file_pos, SEEK_SET );

        timecodes = malloc( timecodes_num * sizeof(double) );
        if( !timecodes )
            return -1;

244 245
        num = 0;
        if( fgets( buff, sizeof(buff), tcfile_in ) != NULL )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
246
        {
247 248
            ret = sscanf( buff, "%lf", &timecodes[0] );
            timecodes[0] *= 1e-3;         /* Timecode format v2 is expressed in milliseconds. */
249
            FAIL_IF_ERROR( ret != 1, "invalid input tcfile for frame 0\n" );
250 251 252 253 254 255 256
            for( num = 1; num < timecodes_num && fgets( buff, sizeof(buff), tcfile_in ) != NULL; )
            {
                if( NO_TIMECODE_LINE )
                    continue;
                ret = sscanf( buff, "%lf", &timecodes[num] );
                timecodes[num] *= 1e-3;         /* Timecode format v2 is expressed in milliseconds. */
                FAIL_IF_ERROR( ret != 1 || timecodes[num] <= timecodes[num - 1],
257
                               "invalid input tcfile for frame %d\n", num );
258 259
                ++num;
            }
Yusuke Nakamura's avatar
Yusuke Nakamura committed
260
        }
261
        FAIL_IF_ERROR( num < timecodes_num, "failed to read input tcfile for frame %d", num );
Yusuke Nakamura's avatar
Yusuke Nakamura committed
262 263 264 265 266 267 268 269 270 271 272

        if( timecodes_num == 1 )
            h->timebase_den = info->fps_num;
        else if( h->auto_timebase_den )
        {
            fpss = malloc( (timecodes_num - 1) * sizeof(double) );
            if( !fpss )
                goto fail;
            for( num = 0; num < timecodes_num - 1; num++ )
            {
                fpss[num] = 1 / (timecodes[num + 1] - timecodes[num]);
273
                if( h->auto_timebase_den )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
274 275
                {
                    int i = 1;
276
                    uint64_t fps_num, fps_den;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
277 278 279 280 281 282
                    double exponent;
                    double fps_sig = sigexp10( fpss[num], &exponent );
                    while( 1 )
                    {
                        fps_den = i * h->timebase_num;
                        fps_num = round( fps_den * fps_sig ) * exponent;
283
                        if( fps_num > UINT32_MAX || fabs( ((double)fps_num / fps_den) / exponent - fps_sig ) < DOUBLE_EPSILON )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
284 285 286
                            break;
                        ++i;
                    }
287 288
                    h->timebase_den = fps_num && h->timebase_den ? lcm( h->timebase_den, fps_num ) : fps_num;
                    if( h->timebase_den > UINT32_MAX )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
289 290 291 292 293 294 295 296 297 298
                    {
                        h->auto_timebase_den = 0;
                        continue;
                    }
                }
            }
            if( h->auto_timebase_num && !h->auto_timebase_den )
                if( try_mkv_timebase_den( fpss, h, timecodes_num - 1 ) < 0 )
                    goto fail;
            free( fpss );
299
            fpss = NULL;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
300 301 302 303 304 305 306 307
        }

        if( timecodes_num > 1 )
            h->assume_fps = 1 / (timecodes[timecodes_num - 1] - timecodes[timecodes_num - 2]);
        else
            h->assume_fps = (double)info->fps_num / info->fps_den;
        h->last_timecode = timecodes[timecodes_num - 1];
    }
308
#undef NO_TIMECODE_LINE
Yusuke Nakamura's avatar
Yusuke Nakamura committed
309 310
    if( h->auto_timebase_den || h->auto_timebase_num )
    {
311 312 313
        uint64_t i = gcd( h->timebase_num, h->timebase_den );
        h->timebase_num /= i;
        h->timebase_den /= i;
314
        x264_cli_log( "timecode", X264_LOG_INFO, "automatic timebase generation %"PRIu64"/%"PRIu64"\n", h->timebase_num, h->timebase_den );
Yusuke Nakamura's avatar
Yusuke Nakamura committed
315
    }
316
    else FAIL_IF_ERROR( h->timebase_den > UINT32_MAX || !h->timebase_den, "automatic timebase generation failed.\n"
317
                        "                  Specify an appropriate timebase manually.\n" );
Yusuke Nakamura's avatar
Yusuke Nakamura committed
318 319 320 321

    h->pts = malloc( h->stored_pts_num * sizeof(int64_t) );
    if( !h->pts )
        goto fail;
322
    for( num = 0; num < h->stored_pts_num; num++ )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
323
    {
324
        h->pts[num] = timecodes[num] * ((double)h->timebase_den / h->timebase_num) + 0.5;
325
        FAIL_IF_ERROR( num > 0 && h->pts[num] <= h->pts[num - 1], "invalid timebase or timecode for frame %d\n", num );
Yusuke Nakamura's avatar
Yusuke Nakamura committed
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
    }

    free( timecodes );
    return 0;

fail:
    if( timecodes )
        free( timecodes );
    if( fpss )
        free( fpss );
    return -1;
}

#undef DOUBLE_EPSILON
#undef MKV_TIMEBASE_DEN

static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt )
{
    int ret = 0;
    FILE *tcfile_in;
    timecode_hnd_t *h = malloc( sizeof(timecode_hnd_t) );
347
    FAIL_IF_ERROR( !h, "malloc failed\n" );
Anton Mitrofanov's avatar
Anton Mitrofanov committed
348
    h->input = cli_input;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
349
    h->p_handle = *p_handle;
350
    h->pts = NULL;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
351
    if( opt->timebase )
352 353 354
    {
        ret = sscanf( opt->timebase, "%"SCNu64"/%"SCNu64, &h->timebase_num, &h->timebase_den );
        if( ret == 1 )
355
        {
356
            h->timebase_num = strtoul( opt->timebase, NULL, 10 );
357 358
            h->timebase_den = 0; /* set later by auto timebase generation */
        }
359
        FAIL_IF_ERROR( h->timebase_num > UINT32_MAX || h->timebase_den > UINT32_MAX,
360
                       "timebase you specified exceeds H.264 maximum\n" );
361
    }
Yusuke Nakamura's avatar
Yusuke Nakamura committed
362 363 364 365 366 367 368
    h->auto_timebase_num = !ret;
    h->auto_timebase_den = ret < 2;
    if( h->auto_timebase_num )
        h->timebase_num = info->fps_den; /* can be changed later by auto timebase generation */
    if( h->auto_timebase_den )
        h->timebase_den = 0;             /* set later by auto timebase generation */

Henrik Gramner's avatar
Henrik Gramner committed
369
    tcfile_in = x264_fopen( psz_filename, "rb" );
370 371
    FAIL_IF_ERROR( !tcfile_in, "can't open `%s'\n", psz_filename );
    if( !x264_is_regular_file( tcfile_in ) )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
372
    {
373
        x264_cli_log( "timecode", X264_LOG_ERROR, "tcfile input incompatible with non-regular file `%s'\n", psz_filename );
Yusuke Nakamura's avatar
Yusuke Nakamura committed
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
        fclose( tcfile_in );
        return -1;
    }

    if( parse_tcfile( tcfile_in, h, info ) < 0 )
    {
        if( h->pts )
            free( h->pts );
        fclose( tcfile_in );
        return -1;
    }
    fclose( tcfile_in );

    info->timebase_num = h->timebase_num;
    info->timebase_den = h->timebase_den;
    info->vfr = 1;

391
    *p_handle = h;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
392 393 394
    return 0;
}

395
static int64_t get_frame_pts( timecode_hnd_t *h, int frame, int real_frame )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
396
{
397 398
    if( frame < h->stored_pts_num )
        return h->pts[frame];
Yusuke Nakamura's avatar
Yusuke Nakamura committed
399 400
    else
    {
401
        if( h->pts && real_frame )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
402
        {
403
            x264_cli_log( "timecode", X264_LOG_INFO, "input timecode file missing data for frame %d and later\n"
404
                          "                 assuming constant fps %.6f\n", frame, h->assume_fps );
Yusuke Nakamura's avatar
Yusuke Nakamura committed
405 406 407
            free( h->pts );
            h->pts = NULL;
        }
408 409 410 411
        double timecode = h->last_timecode + 1 / h->assume_fps;
        if( real_frame )
            h->last_timecode = timecode;
        return timecode * ((double)h->timebase_den / h->timebase_num) + 0.5;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
412
    }
413 414 415 416 417
}

static int read_frame( cli_pic_t *pic, hnd_t handle, int frame )
{
    timecode_hnd_t *h = handle;
418 419
    if( h->input.read_frame( pic, h->p_handle, frame ) )
        return -1;
420 421 422

    pic->pts = get_frame_pts( h, frame, 1 );
    pic->duration = get_frame_pts( h, frame + 1, 0 ) - pic->pts;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
423

424
    return 0;
Yusuke Nakamura's avatar
Yusuke Nakamura committed
425 426
}

427
static int release_frame( cli_pic_t *pic, hnd_t handle )
Yusuke Nakamura's avatar
Yusuke Nakamura committed
428 429 430 431 432 433 434
{
    timecode_hnd_t *h = handle;
    if( h->input.release_frame )
        return h->input.release_frame( pic, h->p_handle );
    return 0;
}

435 436 437 438 439 440 441 442 443 444 445 446
static int picture_alloc( cli_pic_t *pic, hnd_t handle, int csp, int width, int height )
{
    timecode_hnd_t *h = handle;
    return h->input.picture_alloc( pic, h->p_handle, csp, width, height );
}

static void picture_clean( cli_pic_t *pic, hnd_t handle )
{
    timecode_hnd_t *h = handle;
    h->input.picture_clean( pic, h->p_handle );
}

Yusuke Nakamura's avatar
Yusuke Nakamura committed
447 448 449 450 451 452 453 454 455 456
static int close_file( hnd_t handle )
{
    timecode_hnd_t *h = handle;
    if( h->pts )
        free( h->pts );
    h->input.close_file( h->p_handle );
    free( h );
    return 0;
}

457
const cli_input_t timecode_input = { open_file, picture_alloc, read_frame, release_frame, picture_clean, close_file };