/***************************************************************************
                           kdetv_v4l2.cpp
                           --------------
    begin                : Thu Oct 27 2004
    copyright            : (C) 2004 by Dirk Ziegelmeier
    email                : dziegel@gmx.de
***************************************************************************/

/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <qwidget.h>
#include <qradiobutton.h>
#include <qcheckbox.h>
#include <qevent.h>
#include <qdir.h>
#include <qsize.h>
#include <qcolor.h>
#include <qimage.h>

#include <klocale.h>
#include <kdebug.h>
#include <assert.h>
#include <kconfig.h>

#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>

#include "kdetv_v4l2.h"
#include "kdetv_grabber.h"
#include "v4l2dev.h"
#include "qvideostream.h"

#include "kdetv.h"
#include "channel.h"
#include "v4l2configwidget.h"
#include "kdetvvideo/kdetvimagefilter.h"
#include "kdetvvideo/kdetvformatconversionfilter.h"
#include "filtermanager.h"

#include "kdetv_v4l2.moc"

static KdetvImage::ImageFormat qvideoformat2kdetvimageformat(QVideo::ImageFormat fmt);
static QVideo::ImageFormat kdetvimageformat2qvideoformat(KdetvImage::ImageFormat fmt);

KdetvV4L2::KdetvV4L2(Kdetv *ktv, QWidget *parent, const char *name)
    : KdetvSourcePlugin(ktv, "v4l2", parent, name),
      _w(parent),
      _dev(0),
      _probed(false),
      _qvsFormat(QVideo::FORMAT_YUYV),
      _capturing(false),
      _g(0),
      _fieldTime(20000),
      _mostRecentField(KdetvImage::TYPE_INTERLACED_ODD)
{
    _vs = new QVideoStream(_w);
    _formatConversionFilter = new KdetvFormatConversionFilter();

    // figure out best method available
    QVideo::VideoMethod bestAvailable = QVideo::METHOD_XVSHM;
    if (!_vs->haveMethod(bestAvailable)) {
        bestAvailable = QVideo::METHOD_XV;
    }

    _cfg->setGroup("V4L2 Plugin");
    _autoConfig = _cfg->readBoolEntry("Autoconfigure", true);

    if (_autoConfig) {
        _qvsMethod     = bestAvailable;
        _fullFrameRate = false;
    } else {
        _qvsMethod = (QVideo::VideoMethod)_cfg->readNumEntry("GD Method", bestAvailable);

        // saved config may be invalid due to graphics card change
        if (!_vs->haveMethod(_qvsMethod)) {
            _qvsMethod = bestAvailable;
        }
        _fullFrameRate = _cfg->readBoolEntry("Full Frame Rate", false);
    }

    /*
     * The signal to stop capture must be delivered while qApp is in its event loop
     * because the shutdown relies on a working Qt Library Mutex handshake.
     * When qApp is destroyed (at the end of main()), it destroys all QObjects,
     * but without locking the mutex.
     * This makes the thread shutdown crash because the _stop flag is set during object deletion,
     * so the thread can paint to a widget that is being deleted using a buffer that is being
     * deleted (deletion of tv screen and V4L2Grabber).
     */
    connect(qApp, SIGNAL( aboutToQuit() ),
            this, SLOT( stopVideo() ) );

    connect( parent, SIGNAL( resized(int, int) ),
             this, SLOT( viewResized() ) );

    kdDebug() << "Kdetv V4L2 plugin loaded successfully." << endl;
}


KdetvV4L2::~KdetvV4L2()
{
    stopVideo();
    delete _dev;
    _dev = 0;
    delete _vs;
    _vs = 0;
}

// ---------------------------------------------------- Tuner

int KdetvV4L2::setChannelProperties(const Channel::PropertyList& properties)
{
    // This is an optimization to enable faster channel switching
    if((properties["source"].toString() == source())     &&
       (properties["encoding"].toString() == encoding())    ) {
        setFrequency(properties["frequency"].toULongLong());
    } else {
        bool wasCapturing = _capturing;
        stopVideo();
        
        setSource(properties["source"].toString());
        setEncoding(properties["encoding"].toString());
        setFrequency(properties["frequency"].toULongLong());
        
        if(wasCapturing) {
            startVideo();
        }
    }
    return 0;
}

bool KdetvV4L2::isTuner()
{
    return _dev ? _dev->isTuner() : false;
}

int KdetvV4L2::signal()
{
    return _dev ? _dev->signal() : -1;
}

int KdetvV4L2::frequency()
{
    if ( !_dev || !_dev->isTuner() ) {
        return -1;
    }

    return (int)(_dev->frequency() / 1000);
}

void KdetvV4L2::setFrequency( int freq )
{
    if ( !_dev || !_dev->isTuner() ) {
        return;
    }
    _dev->setFrequency((double)freq * 1000);
}

int KdetvV4L2::setSource( const QString &src )
{
    if ( !_dev )
        return -1;

    bool wasCapturing = _capturing;

    stopVideo();
    bool rc = _dev->setSource(src);
    _source = _dev->source();
    if(wasCapturing) {
        startVideo();
    }

    return rc? 0 : -1;
}

int KdetvV4L2::setEncoding( const QString &encoding )
{
    if ( !_dev )
        return -1;

    bool wasCapturing = _capturing;

    stopVideo();
    bool rc = _dev->setEncoding( encoding );
    _encoding = _dev->encoding();

    if(_encoding == "ntsc"     ||
       _encoding == "ntsc-jp"  ||
       _encoding == "pal-m"    ) {
        _fieldTime = 16683;
        // NTSC and PAL-M transmit ODD fields first
        _mostRecentField = KdetvImage::TYPE_INTERLACED_EVEN;
    } else {
        _fieldTime = 20000;
        // all others EVEN fields first
        _mostRecentField = KdetvImage::TYPE_INTERLACED_ODD;
    }
    if(_g) {
        _g->_fieldTime       = _fieldTime;
        _g->_mostRecentField = _mostRecentField;
    }

    if(wasCapturing) {
        startVideo();
    }

    return rc? 0 : -1;
}

// ---------------------------------------------------- Audio

bool KdetvV4L2::muted()
{
    return _dev->control("Mute").toBool();
}

void KdetvV4L2::setMuted( bool muted )
{
    _dev->setControl("Mute", QVariant(muted, 0));
}

bool KdetvV4L2::setVolume( int left, int right )
{
    if(!_dev->hasControl("Volume")) {
        return false;
    }

    // Scale kdetv's internal 0 - 65535 to control range
    int min = _dev->controlMinimum("Volume");
    int max = _dev->controlMaximum("Volume");
    int val = (int)(min + (max-min) * (((double)(left+right)/2) / 65535));

    return _dev->setControl("Volume", val);
}

const QStringList& KdetvV4L2::broadcastedAudioModes()
{
    static QStringList empty;

    if ( !_dev )
        return empty;

    return _dev->broadcastedAudioModes();
}

const QString& KdetvV4L2::defaultAudioMode()
{
    const QStringList& modes = broadcastedAudioModes();

    // FIXME: a more intelligent and general algorithm would not hurt...
    if (modes.contains(i18n("Stereo"))) {
        return *modes.at(modes.findIndex(i18n("Stereo")));
    }

    if (modes.contains(i18n("Language 1"))) {
        return *modes.at(modes.findIndex(i18n("Language 1")));
    }

    return broadcastedAudioModes().first();
}

int KdetvV4L2::setAudioMode( const QString& audioMode )
{
    if ( !_dev )
        return -1;

    return _dev->setAudioMode(audioMode);
}

// ---------------------------------------------------- Device, Controls

QColor KdetvV4L2::colourKey()
{
    return QColor();
}

Control::ControlList& KdetvV4L2::controls()
{
    return _controls;
}

class KdetvV4L2IntegerControl : public IntegerControl
{
public:
    KdetvV4L2IntegerControl(const QString& uiname, const QString& internalName, V4L2Dev* dev)
        : IntegerControl(uiname, internalName),
          _dev(dev)
    {
        advanced     = _dev->controlAdvanced(internalName);
        minimumValue = _dev->controlMinimum(internalName);
        maximumValue = _dev->controlMaximum(internalName);
        defaultValue = _dev->controlDefault(internalName);
        step         = _dev->controlStep(internalName);
    }

    virtual ~KdetvV4L2IntegerControl()
    {
    }

    virtual bool doSetValue(int value)
    {
        return _dev->setControl(internalName, value);
    }

    virtual int value() const
    {
        return _dev->control(internalName).toInt();
    }


private:
    V4L2Dev* _dev;
};

class KdetvV4L2BooleanControl : public BooleanControl
{
public:
    KdetvV4L2BooleanControl(const QString& uiname, const QString& internalName, V4L2Dev* dev)
        : BooleanControl(uiname, internalName),
          _dev(dev)
    {
        advanced     = _dev->controlAdvanced(internalName);
        defaultValue = _dev->controlDefault(internalName);
    }

    virtual ~KdetvV4L2BooleanControl()
    {
    }

    virtual bool doSetValue(bool value)
    {
        return _dev->setControl(internalName, QVariant(value, 0));
    }

    virtual bool value() const
    {
        return _dev->control(internalName).toInt();
    }


private:
    V4L2Dev* _dev;
};

class KdetvV4L2MenuControl : public MenuControl
{
public:
    KdetvV4L2MenuControl(const QString& uiname, const QString& internalName, V4L2Dev* dev)
        : MenuControl(uiname, internalName),
          _dev(dev)
    {
        advanced     = _dev->controlAdvanced(internalName);
        defaultValue = _dev->controlDefault(internalName);
        choices      = _dev->controlMenuChoices(internalName);
    }

    virtual ~KdetvV4L2MenuControl()
    {
    }

    virtual bool doSetValue(const QString& value)
    {
        return _dev->setControl(internalName, value);
    }

    virtual const QString value() const
    {
        return _dev->control(internalName).toString();
    }


private:
    V4L2Dev* _dev;
};

class KdetvV4L2ButtonControl : public ButtonControl
{
public:
    KdetvV4L2ButtonControl(const QString& uiname, const QString& internalName, V4L2Dev* dev)
        : ButtonControl(uiname, internalName),
          _dev(dev)
    {
        advanced = _dev->controlAdvanced(internalName);
    }

    virtual ~KdetvV4L2ButtonControl()
    {
    }

    virtual bool click()
    {
        return _dev->setControl(internalName, QVariant());
    }

private:
    V4L2Dev* _dev;
};

int KdetvV4L2::setDevice( const QString &name )
{
    if (!_probed)
        probeDevices();

    if (_dev) {
        stopVideo();
        delete _dev;
    }

    _device = name;
    _currentDev = _devNames[name];
    kdDebug() << "V4L2: setDevice [" << name
              << "] which maps to " << _currentDev << endl;

    _dev = V4L2Dev::getDevice( _currentDev );
    kdDebug() << "V4L2: Success? " << (_dev ? "true" : "false") << endl;

    _audioModes.clear();
    if (_dev) {
        _audioModes += _dev->audioModes();
    }

    _audioModes.clear();
    _controls.clear();
    if(_dev) {
        _audioModes += _dev->audioModes();

        const QStringList& controls = _dev->controls();
        for(QStringList::const_iterator it = controls.constBegin();
            it != controls.constEnd();
            ++it) {
            if(((*it).lower() == "volume") ||
               ((*it).lower() == "mute")      ) {
                continue;
            }

            switch(_dev->controlType(*it)) {
            case V4L2Dev::ControlType_Int:
                _controls.append(new KdetvV4L2IntegerControl(i18n((*it).ascii()), *it, _dev));
                break;
            case V4L2Dev::ControlType_Boolean:
                _controls.append(new KdetvV4L2BooleanControl(i18n((*it).ascii()), *it, _dev));
                break;
            case V4L2Dev::ControlType_Button:
                _controls.append(new KdetvV4L2ButtonControl(i18n((*it).ascii()), *it, _dev));
                break;
            case V4L2Dev::ControlType_Menu:
                _controls.append(new KdetvV4L2MenuControl(i18n((*it).ascii()), *it, _dev));
                break;
            default:
                break;
            }
        }
    }

    return _dev ? 0 : -1;
}

int KdetvV4L2::probeDevices()
{
    QString dev;
    struct stat sb;


    // don't probe multiple times, it's unnecessary and might yield incorrect
    // results if plugin is currently using the device
    if (_probed) return 0;

    int rc = stat("/dev/v4l", &sb);
    if (!rc && S_ISDIR(sb.st_mode) && !access("/dev/v4l", R_OK|X_OK)) {
        // DEVFS
        dev = "/dev/v4l/video%1";
    } else {
        // Normal V4L2 case
        dev = "/dev/video%1";
    }

    _devices.clear();
    _sources.clear();
    _tuners.clear();
    _encodings.clear();
    _devNames.clear();

    QString mainVideoDev = QString::null;
    // FIXME: when we test for devices, we must make sure that we can
    // actually use them too.  This fails for cases where something else
    // is using the device currently.
    if (!access("/dev/video", R_OK|W_OK)) {
        V4L2Dev *vd = V4L2Dev::getDevice("/dev/video");
        if (vd) {
            QString name = "Video4Linux2: " + vd->name();
            _tuners[name] = vd->isTuner();
            _sources[name] = vd->sources();
            _encodings[name] = vd->encodings();
            _devices.append(name);
            _devNames[name] = "/dev/video";
            mainVideoDev = QDir("/dev/video").canonicalPath();
            delete vd;
        }
    }

    for (int i = 0; i <= 9; i++) {
        QString dn = dev.arg(i);
        if (dn != mainVideoDev && !access(dn.local8Bit(), R_OK|W_OK)) {
            V4L2Dev *vd = V4L2Dev::getDevice(dn);
            if (vd) {
                QString name = i18n("Video4Linux2: ") + vd->name();
                _tuners[name] = vd->isTuner();
                _sources[name] = vd->sources();
                _encodings[name] = vd->encodings();
                _devices.append(name);
                _devNames[name] = dn;
                delete vd;
            }
        }
    }

    _probed = true;
    return 0;
}

// ---------------------------------------------------- Display

void KdetvV4L2::viewResized()
{
    V4L2GrabberLocker l(_g);
    if (!_dev)
        return;

    if(_capturing) {
        stopVideo();
        startVideo();
    }
}

// ---------------------------------------------------- Playback

int KdetvV4L2::startVideo()
{
    if(!_dev || _capturing) {
        return -1;
    }

    _vs->setMethod(_qvsMethod);
    _vs->setFormat(_qvsFormat);
    _vs->setSize(_w->size());

    KdetvImageFilterChain* c = driver()->filterManager()->filterChain();

    calculateGrabFormat(c, _formatConversionFilter);
    kdDebug() << c->filterChainStatus() << endl;

    _dev->startStreaming(3);

    setMuted(false);

    _g = new V4L2Grabber(this, _dev, _vs, qvideoformat2kdetvimageformat(_dev->inputFormat()));
    _g->_flt             = c;
    _g->_fmtConv         = _formatConversionFilter;
    _g->_fieldTime       = _fieldTime;
    _g->_mostRecentField = _mostRecentField;
    _g->_fullFrameRate   = _fullFrameRate;
    _g->start();

    _capturing = true;

    return 0;
}

int KdetvV4L2::stopVideo()
{
    if (!_capturing)
        return -1;

    _dev->stopStreaming();
    _g->stop();
    _g = 0;

    setMuted(true);

    _capturing = false;
    return 0;
}

void KdetvV4L2::calculateGrabFormat(KdetvImageFilterChain* c, KdetvFormatConversionFilter* f)
{
    KdetvImage::ImageFormat outputFormat = qvideoformat2kdetvimageformat(_vs->formatsForMethod(_qvsMethod));

    kdDebug() << "Trying to build output chain without conversion..." << endl;
    c->setOutputFormat(outputFormat);

    // Try grabbing the format the display needs (-> hopefully no conversion necessary)
    if(_dev->setInputProperties(_vs->formatsForMethod(_qvsMethod), _w->size()).isValid()) {
        c->setInputFormat(qvideoformat2kdetvimageformat(_dev->inputFormat()));
        if(c->isValid()) {
            f->setInputFormat(outputFormat);
            f->setOutputFormat(outputFormat);
            kdDebug() << "... successful." << endl;
            return;
        }
    }

    // No success, try using a use format conversion filter
    kdDebug() << "... failed. Trying to use format converter..." << endl;
    KdetvImage::ImageFormat supportedFormats = f->inputFormats();
    KdetvImage::ImageFormat format = (KdetvImage::ImageFormat)1;
    do {
        if(supportedFormats & format) {
            f->setInputFormat(format);

            KdetvImage::ImageFormat fmt = (KdetvImage::ImageFormat)1;
            do {
                if(f->outputFormats() & c->inputFormats() & fmt) {
                    if(_dev->setInputProperties(kdetvimageformat2qvideoformat(format), _w->size()).isValid()) {
                        kdDebug() << "Trying to grab " << KdetvImage::toString(format)
                                  << " and convert it to " << KdetvImage::toString(fmt) << endl;
                        f->setOutputFormat(fmt);
                        c->setInputFormat(fmt);
                        if(c->isValid()) {
                            return;
                        }
                    }
                }
                fmt = (KdetvImage::ImageFormat)(fmt<<1);
            } while((unsigned)fmt<0x80000000);
        }
        format = (KdetvImage::ImageFormat)(format<<1);
    } while((unsigned)format<0x80000000);

    // No success. Use display format for grabbing and pray it works.
    kdWarning() << "... failed. kdetv likely does not to work with your device and/or your current filter config." << endl;
    _dev->setInputProperties(_vs->formatsForMethod(_qvsMethod), _w->size());
    c->setInputFormat(qvideoformat2kdetvimageformat(_dev->inputFormat()));
    return;
}

bool KdetvV4L2::videoPlaying() const
{
    return _capturing;
}

// ---------------------------------------------------- View modes

bool KdetvV4L2::canVideoDesktop() const
{
    return false;
}

// ---------------------------------------------------- Snapshots

bool KdetvV4L2::canGrabStill() const
{
    return true;
}

bool KdetvV4L2::grabStill(QImage* img)
{
    bool rc = false;
    bool wasCapturing = _capturing;
    stopVideo();

    // FIXME: hardcoded image format and endianess
    KdetvImage im;
    im.createBuffer(img->width() * img->height() * 4);
    im.setFormat(KdetvImage::FORMAT_BGR24);

    im.setSize(_dev->snapshot(im.buffer(), img->size(), QVideo::FORMAT_BGR24));

    if(im.size().isValid()) {
        im.toQImage(*img);
        rc = true;
    }

    if(wasCapturing) {
        startVideo();
    }

    return rc;
}

// ---------------------------------------------------- Configuration

QWidget *KdetvV4L2::configWidget(QWidget *parent, const char *name)
{
    _cfgWidget = new V4L2ConfigWidget(parent, name);

    _cfgWidget->_xvideo->setEnabled(_vs->haveMethod(QVideo::METHOD_XV));
    _cfgWidget->_xvshm->setEnabled (_vs->haveMethod(QVideo::METHOD_XVSHM));

    switch(_qvsMethod) {
    case QVideo::METHOD_XV:
        _cfgWidget->_xvideo->setChecked(true);
        break;
    case QVideo::METHOD_XVSHM:
        _cfgWidget->_xvshm->setChecked(true);
        break;
    default:
        assert(0);
        break;
    }

    _cfgWidget->_autoConfig->setChecked(_autoConfig);
    if(_fullFrameRate) {
        _cfgWidget->_frameRateFull->setChecked(true);
    } else {
        _cfgWidget->_frameRateHalf->setChecked(true);
    }

    return _cfgWidget;
}

void KdetvV4L2::saveConfig()
{
    _autoConfig    = _cfgWidget->_autoConfig->isChecked();
    _fullFrameRate = _cfgWidget->_frameRateFull->isChecked();

    _qvsMethod = QVideo::METHOD_NONE;
    if (_cfgWidget->_xvideo->isChecked()) {
        _qvsMethod = QVideo::METHOD_XV;
    } else if (_cfgWidget->_xvshm->isChecked()) {
        _qvsMethod = QVideo::METHOD_XVSHM;
    }

    _cfg->writeEntry("GD Method", _qvsMethod);
    _cfg->writeEntry("Autoconfigure", _autoConfig);
    _cfg->writeEntry("Full Frame Rate", _fullFrameRate);

    if (_capturing) {
        stopVideo();
        _vs->setMethod(_qvsMethod);
        startVideo();
    } else {
        _vs->setMethod(_qvsMethod);
    }

    _cfg->sync();
}

// ---------------------------------------------------- Misc

static KdetvImage::ImageFormat qvideoformat2kdetvimageformat(QVideo::ImageFormat fmt)
{
    if(fmt & QVideo::FORMAT_GREY) {
        return KdetvImage::FORMAT_GREY;
    }
    if(fmt & QVideo::FORMAT_HI240) {
        return KdetvImage::FORMAT_HI240;
    }
    if(fmt & QVideo::FORMAT_RGB15_LE) {
        return KdetvImage::FORMAT_RGB15_LE;
    }
    if(fmt & QVideo::FORMAT_RGB16_LE) {
        return KdetvImage::FORMAT_RGB16_LE;
    }
    if(fmt & QVideo::FORMAT_RGB15_BE) {
        return KdetvImage::FORMAT_RGB15_BE;
    }
    if(fmt & QVideo::FORMAT_RGB16_BE) {
        return KdetvImage::FORMAT_RGB16_BE;
    }
    if(fmt & QVideo::FORMAT_RGB24) {
        return KdetvImage::FORMAT_RGB24;
    }
    if(fmt & QVideo::FORMAT_RGB32) {
        return KdetvImage::FORMAT_RGB32;
    }
    if(fmt & QVideo::FORMAT_BGR24) {
        return KdetvImage::FORMAT_BGR24;
    }
    if(fmt & QVideo::FORMAT_BGR32) {
        return KdetvImage::FORMAT_BGR32;
    }
    if(fmt & QVideo::FORMAT_YUYV) {
        return KdetvImage::FORMAT_YUYV;
    }
    if(fmt & QVideo::FORMAT_UYVY) {
        return KdetvImage::FORMAT_UYVY;
    }
    if(fmt & QVideo::FORMAT_YUV422P) {
        return KdetvImage::FORMAT_YUV422P;
    }
    if(fmt & QVideo::FORMAT_YUV420P) {
        return KdetvImage::FORMAT_YUV420P;
    }
    return KdetvImage::FORMAT_NONE;
}

static QVideo::ImageFormat kdetvimageformat2qvideoformat(KdetvImage::ImageFormat fmt)
{
    if(fmt & KdetvImage::FORMAT_GREY) {
        return QVideo::FORMAT_GREY;
    }
    if(fmt & KdetvImage::FORMAT_HI240) {
        return QVideo::FORMAT_HI240;
    }
    if(fmt & KdetvImage::FORMAT_RGB15_LE) {
        return QVideo::FORMAT_RGB15_LE;
    }
    if(fmt & KdetvImage::FORMAT_RGB16_LE) {
        return QVideo::FORMAT_RGB16_LE;
    }
    if(fmt & KdetvImage::FORMAT_RGB15_BE) {
        return QVideo::FORMAT_RGB15_BE;
    }
    if(fmt & KdetvImage::FORMAT_RGB16_BE) {
        return QVideo::FORMAT_RGB16_BE;
    }
    if(fmt & KdetvImage::FORMAT_RGB24) {
        return QVideo::FORMAT_RGB24;
    }
    if(fmt & KdetvImage::FORMAT_RGB32) {
        return QVideo::FORMAT_RGB32;
    }
    if(fmt & KdetvImage::FORMAT_BGR24) {
        return QVideo::FORMAT_BGR24;
    }
    if(fmt & KdetvImage::FORMAT_BGR32) {
        return QVideo::FORMAT_BGR32;
    }
    if(fmt & KdetvImage::FORMAT_YUYV) {
        return QVideo::FORMAT_YUYV;
    }
    if(fmt & KdetvImage::FORMAT_UYVY) {
        return QVideo::FORMAT_UYVY;
    }
    if(fmt & KdetvImage::FORMAT_YUV422P) {
        return QVideo::FORMAT_YUV422P;
    }
    if(fmt & KdetvImage::FORMAT_YUV420P) {
        return QVideo::FORMAT_YUV420P;
    }
    return QVideo::FORMAT_NONE;
}

bool KdetvV4L2::event(QEvent* e)
{
    if (e->type() == QEvent::User) {
        emit errorMessage( ((V4L2ErrorEvent*)e)->_msg );
        stopVideo();
        return true;
    } else {
        return KdetvSourcePlugin::event(e);
    }
}

// ---------------------------------------------------- Factory

extern "C" {
    KDETV_EXPORT KdetvV4L2* create_v4l2(Kdetv *ktv, QWidget *w) {
        return new KdetvV4L2(ktv, w, "v4l2 plugin");
    }
}
