def vid2h5(data: np.ndarray, ut1, rawind, ticks, outfn: Path, P: dict, i: int=0,
           Nfile: int=1, det=None, tstart=None, cmdlog: str=None):
    assert outfn, 'must provide a filename to write'

    outfn = Path(outfn).expanduser()
    assert not outfn.is_dir(
    ), 'must provide FILEname, you provided directory {outfn}'
    outfn.parent.mkdir(exist_ok=True)

    txtupd = f'convert file {i} / {Nfile}'
    if 'spoolfn' in P:
        txtupd += f' {P["spoolfn"].name}'
    txtupd += f' => {outfn}'
    """
    note if line wraps (>80 characters), this in-place update breaks.
    """
    print(txtupd + '\r', end="")
    # %% saving
    if outfn.suffix == '.h5':
        """
        Reference: https://www.hdfgroup.org/HDF5/doc/ADGuide/ImageSpec.html
        Thanks to Eric Piel of Delmic for pointing out this spec
        * the HDF5 attributess set are necessary to put HDFView into image mode and enables
        other conforming readers to easily play images stacks as video.
        * the string_() calls are necessary to make fixed length strings per HDF5 spec
        """
        # NOTE write mode r+ to not overwrite incremental images
        if outfn.is_file():
            writemode = 'r+'  # append
        else:
            writemode = 'w'

        if data is not None:

            N = data.shape[0] * Nfile
            ind = slice(i * data.shape[0], (i + 1) * data.shape[0])
        else:  # FIXME haven't reevaluated for update only case
            for q in (ut1, rawind, ticks):
                if q is not None:
                    N = len(q)
                    break
            ind = slice(None)

        with h5py.File(outfn, writemode) as f:
            if data is not None:
                if 'rawimg' not in f:  # first run
                    setupimgh5(f, N, data.shape[1], data.shape[2])

                f['/rawimg'][ind, ...] = data

            if ut1 is not None:
                print(f'writing from {datetime.utcfromtimestamp(ut1[0])}'
                      f'to {datetime.utcfromtimestamp(ut1[-1])}')
                if 'ut1_unix' not in f:
                    fut1 = f.create_dataset(
                        '/ut1_unix', shape=(N,), dtype=float, fletcher32=True)
                    fut1.attrs['units'] = 'seconds since Unix epoch Jan 1 1970 midnight'

                f['/ut1_unix'][ind] = ut1

            if tstart is not None and 'tstart' not in f:
                f['/tstart'] = tstart

            if rawind is not None:
                if 'rawind' not in f:
                    fri = f.create_dataset(
                        '/rawind', shape=(N,), dtype=np.int64, fletcher32=True)
                    fri.attrs['units'] = 'one-based index since camera program started this session'

                f['/rawind'][ind] = rawind

            if ticks is not None:
                if 'ticks' not in f:
                    ftk = f.create_dataset(
                        '/ticks', shape=(N,), dtype=np.uint64, fletcher32=True)  # Uint64
                    ftk.attrs['units'] = 'FPGA tick counter for each image frame'

                f['/ticks'][ind] = ticks

            if 'spoolfn' in P:
                # http://docs.h5py.org/en/latest/strings.html
                if 'spoolfn' not in f:
                    fsp = f.create_dataset(
                        '/spoolfn', shape=(N,), dtype=h5py.special_dtype(vlen=bytes))
                    fsp.attrs['description'] = 'input filename data was extracted from'

                f['/spoolfn'][ind] = P['spoolfn'].name

            if det is not None:
                if 'detect' not in f:
                    fdt = f.create_dataset('/detect', shape=(N,), dtype=int)
                    fdt.attrs['description'] = '# of auroral detections this frame'

                f['/detect'][i] = det[i]

            if 'params' not in f:
                cparam = np.array((
                    P['kineticsec'],
                    P['rotccw'],
                    P['transpose'],
                    P['flipud'],
                    P['fliplr'],
                    1),
                    dtype=[('kineticsec', 'f8'),
                           ('rotccw', 'i1'),
                           ('transpose', 'i1'),
                           ('flipud', 'i1'),
                           ('fliplr', 'i1'),
                           ('questionable_ut1', 'i1')]
                )

                # cannot use fletcher32 here, typeerror
                f.create_dataset('/params', data=cparam)

            if 'sensorloc' not in f and 'sensorloc' in P:
                loc = P['sensorloc']
                lparam = np.array((loc[0], loc[1], loc[2]),
                                  dtype=[('lat', 'f8'), ('lon', 'f8'), ('alt_m', 'f8')])

                # cannot use fletcher32 here, typeerror
                Ld = f.create_dataset('/sensorloc', data=lparam)
                Ld.attrs['units'] = 'WGS-84 lat (deg),lon (deg), altitude (meters)'

            if 'cmdlog' not in f:
                if isinstance(cmdlog, (tuple, list)):
                    cmdlog = ' '.join(cmdlog)
                # cannot use fletcher32 here, typeerror
                f['/cmdlog'] = str(cmdlog)

            if 'header' not in f and 'header' in P:
                f['/header'] = str(P['header'])

            if 'hdf5version' not in f:
                f['/hdf5version'] = h5py.version.hdf5_version_tuple

    elif outfn.suffix == '.fits':
        from astropy.io import fits
        # NOTE the with... syntax does NOT yet work with astropy.io.fits
        hdu = fits.PrimaryHDU(data)
        hdu.writeto(outfn, clobber=False, checksum=True)
        # no close
        """
        Note: the orientation of this FITS in NASA FV program and the preview
        image shown in Python should/must have the same orientation and pixel indexing')
        """

    elif outfn.suffix == '.mat':
        from scipy.io import savemat
        # matlab is fortran order
        matdata = {'imgdata': data.transpose(1, 2, 0)}
        savemat(outfn, matdata, oned_as='column')
    else:
        raise ValueError(f'what kind of file is {outfn}')