summaryrefslogtreecommitdiffstats
path: root/xine_artsplugin/tools
diff options
context:
space:
mode:
Diffstat (limited to 'xine_artsplugin/tools')
-rw-r--r--xine_artsplugin/tools/Makefile.am1
-rw-r--r--xine_artsplugin/tools/thumbnail/Makefile.am24
-rw-r--r--xine_artsplugin/tools/thumbnail/sprocket-large.pngbin0 -> 1033 bytes
-rw-r--r--xine_artsplugin/tools/thumbnail/sprocket-medium.pngbin0 -> 649 bytes
-rw-r--r--xine_artsplugin/tools/thumbnail/sprocket-small.pngbin0 -> 357 bytes
-rw-r--r--xine_artsplugin/tools/thumbnail/videocreator.cpp376
-rw-r--r--xine_artsplugin/tools/thumbnail/videocreator.h40
-rw-r--r--xine_artsplugin/tools/thumbnail/videoscaler.cpp258
-rw-r--r--xine_artsplugin/tools/thumbnail/videoscaler.h24
-rw-r--r--xine_artsplugin/tools/thumbnail/videothumbnail.desktop71
10 files changed, 794 insertions, 0 deletions
diff --git a/xine_artsplugin/tools/Makefile.am b/xine_artsplugin/tools/Makefile.am
new file mode 100644
index 00000000..1ed9efda
--- /dev/null
+++ b/xine_artsplugin/tools/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS=thumbnail
diff --git a/xine_artsplugin/tools/thumbnail/Makefile.am b/xine_artsplugin/tools/thumbnail/Makefile.am
new file mode 100644
index 00000000..40358fda
--- /dev/null
+++ b/xine_artsplugin/tools/thumbnail/Makefile.am
@@ -0,0 +1,24 @@
+## $Id$
+## Makefile.am of kdemultimedia/tools/thumbnail
+
+INCLUDES = -I$(kde_includes)/arts $(all_includes) $(XINE_CFLAGS)
+
+AM_CFLAGS = -U__STRICT_ANSI__
+
+METASOURCES = AUTO
+
+kde_module_LTLIBRARIES = videothumbnail.la
+
+videothumbnail_la_SOURCES = videocreator.cpp videoscaler.cpp
+videothumbnail_la_LIBADD = $(XINE_LIBS) $(LIB_KDECORE)
+videothumbnail_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) -pthread
+
+noinst_HEADERS = videocreator.h
+
+xineartsplugin_tools_videothumbnail_DATA = sprocket-small.png sprocket-medium.png sprocket-large.png
+
+xineartsplugin_tools_videothumbnaildir = $(kde_datadir)/videothumbnail
+
+services_DATA = videothumbnail.desktop
+
+servicesdir = $(kde_servicesdir)
diff --git a/xine_artsplugin/tools/thumbnail/sprocket-large.png b/xine_artsplugin/tools/thumbnail/sprocket-large.png
new file mode 100644
index 00000000..6c0e65a9
--- /dev/null
+++ b/xine_artsplugin/tools/thumbnail/sprocket-large.png
Binary files differ
diff --git a/xine_artsplugin/tools/thumbnail/sprocket-medium.png b/xine_artsplugin/tools/thumbnail/sprocket-medium.png
new file mode 100644
index 00000000..8868e660
--- /dev/null
+++ b/xine_artsplugin/tools/thumbnail/sprocket-medium.png
Binary files differ
diff --git a/xine_artsplugin/tools/thumbnail/sprocket-small.png b/xine_artsplugin/tools/thumbnail/sprocket-small.png
new file mode 100644
index 00000000..62fa3fd7
--- /dev/null
+++ b/xine_artsplugin/tools/thumbnail/sprocket-small.png
Binary files differ
diff --git a/xine_artsplugin/tools/thumbnail/videocreator.cpp b/xine_artsplugin/tools/thumbnail/videocreator.cpp
new file mode 100644
index 00000000..94e87b48
--- /dev/null
+++ b/xine_artsplugin/tools/thumbnail/videocreator.cpp
@@ -0,0 +1,376 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2002 Simon MacMullen
+ Copyright (C) 2003 Ewald Snel <ewald@rambo.its.tudelft.nl>
+
+ 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., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+// $Id$
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <pthread.h>
+#include <sys/time.h>
+
+#include <qpixmap.h>
+#include <qdialog.h>
+#include <qfile.h>
+#include <qimage.h>
+#include <qpainter.h>
+#include <qpaintdevice.h>
+
+#include <iostream>
+
+#include <kstandarddirs.h>
+#include <kapplication.h>
+
+#define XINE_ENABLE_EXPERIMENTAL_FEATURES 1
+
+#include <xine.h>
+
+#include "videocreator.h"
+#include "videoscaler.h"
+
+#define TIMEOUT 15 // 15 seconds
+#define MAX_ATTEMPTS 25
+
+
+// Global xine pointer
+static xine_t *xine_shared = NULL;
+static pthread_mutex_t xine_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t xine_cond = PTHREAD_COND_INITIALIZER;
+static int xineRefCount = 0;
+
+static void xine_init_routine()
+{
+ char cfgFileName[272];
+
+ xine_shared = (xine_t *)xine_new();
+
+ snprintf( cfgFileName, 272, "%s/.xine/config", getenv( "HOME" ) );
+
+ xine_config_load( xine_shared, (const char *)cfgFileName );
+
+ xine_init( xine_shared );
+}
+
+static void *xine_timeout_routine( void * )
+{
+ pthread_mutex_lock( &xine_mutex );
+
+ while (xine_shared != 0)
+ {
+ if (xineRefCount == 0)
+ {
+ struct timespec ts;
+ struct timeval tv;
+
+ gettimeofday( &tv, 0 );
+
+ ts.tv_sec = tv.tv_sec;
+ ts.tv_nsec = tv.tv_usec * 1000;
+ ts.tv_sec += TIMEOUT;
+
+ if (pthread_cond_timedwait( &xine_cond, &xine_mutex, &ts ) != 0 &&
+ xineRefCount == 0)
+ {
+ xine_exit( xine_shared );
+ xine_shared = NULL;
+ break;
+ }
+ }
+ else
+ {
+ pthread_cond_wait( &xine_cond, &xine_mutex );
+ }
+ }
+ pthread_mutex_unlock( &xine_mutex );
+
+ return NULL;
+}
+
+static xine_t *xine_shared_init()
+{
+ pthread_mutex_lock( &xine_mutex );
+
+ ++xineRefCount;
+
+ if (xine_shared == 0)
+ {
+ pthread_t thread;
+
+ xine_init_routine();
+
+ if (pthread_create( &thread, NULL, xine_timeout_routine, NULL ) == 0)
+ {
+ pthread_detach( thread );
+ }
+ }
+ else
+ {
+ pthread_cond_signal( &xine_cond );
+ }
+ pthread_mutex_unlock( &xine_mutex );
+
+ return xine_shared;
+}
+
+static void xine_shared_exit( xine_t * )
+{
+ pthread_mutex_lock( &xine_mutex );
+
+ if (--xineRefCount == 0)
+ {
+ pthread_cond_signal( &xine_cond );
+ }
+ pthread_mutex_unlock( &xine_mutex );
+}
+
+static QImage createThumbnail( xine_video_frame_t *frame, int width, int height )
+{
+ unsigned char *base[3];
+ unsigned int pitches[3];
+
+ if ((frame->aspect_ratio * height) > width)
+ height = (int)(.5 + (width / frame->aspect_ratio));
+ else
+ width = (int)(.5 + (height * frame->aspect_ratio));
+
+ QImage image( width, height, 32 );
+
+ if (frame->colorspace == XINE_IMGFMT_YV12)
+ {
+ int y_size, uv_size;
+
+ pitches[0] = (frame->width + 7) & ~0x7;
+ pitches[1] = (((frame->width + 1) / 2) + 7) & ~0x7;
+ pitches[2] = pitches[1];
+
+ y_size = pitches[0] * frame->height;
+ uv_size = pitches[1] * ((frame->height + 1) / 2);
+
+ base[0] = frame->data;
+ base[1] = base[0] + y_size + uv_size;
+ base[2] = base[0] + y_size;
+
+ scaleYuvToRgb32( frame->width, frame->height, base, pitches,
+ width, height, (unsigned int *)image.bits(),
+ image.bytesPerLine() );
+ }
+ else if (frame->colorspace == XINE_IMGFMT_YUY2)
+ {
+ pitches[0] = 2*((frame->width + 3) & ~0x3);
+ base[0] = frame->data;
+
+ scaleYuy2ToRgb32( frame->width, frame->height, base[0], pitches[0],
+ width, height, (unsigned int *)image.bits(),
+ image.bytesPerLine() );
+ }
+ return image;
+}
+
+// Return the variance of the brightness of the pixels
+static double imageVariance( unsigned char *pixels, int pitch,
+ int width, int height, int step )
+{
+ double sigmaX = 0;
+ double sigmaXSquared = 0;
+
+ for (int y=0; y < height ; y++)
+ {
+ unsigned int uSigmaX = 0;
+ unsigned int uSigmaXSquared = 0;
+
+ for (int x=0, n=(width * step); x < n ; x+=step)
+ {
+ int gray = pixels[x];
+
+ uSigmaX += gray;
+ uSigmaXSquared += gray * gray;
+ }
+
+ sigmaX += uSigmaX;
+ sigmaXSquared += uSigmaXSquared;
+
+ pixels += pitch;
+ }
+
+ unsigned int total = height * width;
+
+ return sqrt( sigmaXSquared / total - (sigmaX / total) * (sigmaX / total) );
+}
+
+static bool findBestFrame( xine_video_port_t *vo_port, xine_video_frame_t *frame )
+{
+ xine_video_frame_t frames[2], *bestFrame = NULL;
+ double variance, bestVariance = 0;
+
+ for (int i=0, n=0; i < MAX_ATTEMPTS; i++)
+ {
+ xine_video_frame_t *cFrame = &frames[n];
+
+ // Try to read next frame
+ if (!xine_get_next_video_frame( vo_port, cFrame ))
+ {
+ break;
+ }
+
+ variance = imageVariance( cFrame->data, ((cFrame->width + 7) & ~0x7),
+ cFrame->width, cFrame->height,
+ (cFrame->colorspace == XINE_IMGFMT_YV12) ? 1 : 2 );
+
+ // Compare current frame to best frame
+ if (bestFrame == NULL || variance > bestVariance)
+ {
+ if (bestFrame != NULL)
+ {
+ xine_free_video_frame( vo_port, bestFrame );
+ }
+
+ bestFrame = cFrame;
+ bestVariance = variance;
+
+ n = (1 - n);
+ }
+ else
+ {
+ xine_free_video_frame( vo_port, cFrame );
+ }
+
+ // Stop searching if current frame is interesting enough
+ if (variance > 40.0)
+ {
+ break;
+ }
+ }
+
+ // This should be the best frame to create a thumbnail from
+ if (bestFrame != NULL)
+ {
+ *frame = *bestFrame;
+ }
+ return (bestFrame != NULL);
+}
+
+
+extern "C"
+{
+ ThumbCreator *new_creator()
+ {
+ return new VideoCreator;
+ }
+}
+
+VideoCreator::VideoCreator()
+{
+}
+
+VideoCreator::~VideoCreator()
+{
+}
+
+bool VideoCreator::create(const QString &path, int width, int height, QImage &img)
+{
+ if (m_sprocketSmall.isNull())
+ {
+ QString pixmap = locate( "data", "videothumbnail/sprocket-small.png" );
+ m_sprocketSmall = QPixmap(pixmap);
+ pixmap = locate( "data", "videothumbnail/sprocket-medium.png" );
+ m_sprocketMedium = QPixmap(pixmap);
+ pixmap = locate( "data", "videothumbnail/sprocket-large.png" );
+ m_sprocketLarge = QPixmap(pixmap);
+ }
+
+ // The long term plan is to seek to frame 1, create thumbnail, see if is is
+ // interesting enough, if not seek to frame 2, then 4, then 8, etc.
+ // "Interesting enough" means the variance of the pixel brightness is high. This
+ // is because many videos fade up from black and a black rectangle is boring.
+ //
+ // But for the time being we can't seek so we just let it play for one second
+ // then take whatever we find.
+
+ xine_t *xine = xine_shared_init();
+ xine_audio_port_t *ao_port = xine_new_framegrab_audio_port( xine );
+ xine_video_port_t *vo_port = xine_new_framegrab_video_port( xine );
+ xine_stream_t *stream = xine_stream_new( xine, ao_port, vo_port );
+ bool success = false;
+
+ if (xine_open( stream, QFile::encodeName ( path ).data() ))
+ {
+ xine_video_frame_t frame;
+ int length;
+
+ // Find 'best' (or at least any) frame
+ if (!xine_get_pos_length( stream, NULL, NULL, &length ) || length > 5000)
+ {
+ if (xine_play( stream, 0, 4000 ))
+ {
+ success = findBestFrame( vo_port, &frame );
+ }
+ }
+ if (!success)
+ {
+ // Some codecs can't seek to start, but close/open works
+ xine_close( stream );
+ xine_open( stream, path.ascii() );
+
+ if (xine_play( stream, 0, 0 ))
+ {
+ success = findBestFrame( vo_port, &frame );
+ }
+ }
+
+ // Create thumbnail image
+ if (success)
+ {
+ QPixmap pix( createThumbnail( &frame, width, height ) );
+ QPainter painter( &pix );
+ QPixmap sprocket;
+
+ if (pix.height() < 60)
+ sprocket = m_sprocketSmall;
+ else if (pix.height() < 90)
+ sprocket = m_sprocketMedium;
+ else
+ sprocket = m_sprocketLarge;
+
+ for (int y = 0; y < pix.height() + sprocket.height(); y += sprocket.height()) {
+ painter.drawPixmap( 0, y, sprocket );
+ }
+
+ img = pix.convertToImage();
+
+ xine_free_video_frame( vo_port, &frame );
+ }
+
+ xine_stop( stream );
+ }
+
+ xine_dispose( stream );
+ xine_close_audio_driver( xine, ao_port );
+ xine_close_video_driver( xine, vo_port );
+ xine_shared_exit( xine );
+
+ return (success);
+}
+
+ThumbCreator::Flags VideoCreator::flags() const
+{
+ return (ThumbCreator::Flags) (DrawFrame);
+}
+
+#include "videocreator.moc"
diff --git a/xine_artsplugin/tools/thumbnail/videocreator.h b/xine_artsplugin/tools/thumbnail/videocreator.h
new file mode 100644
index 00000000..bd16e6e2
--- /dev/null
+++ b/xine_artsplugin/tools/thumbnail/videocreator.h
@@ -0,0 +1,40 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2002 Simon MacMullen
+
+ 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., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+#ifndef _VIDEOCREATOR_H_
+#define _VIDEOCREATOR_H_ "$Id$"
+
+#include <kio/thumbcreator.h>
+
+class VideoCreator : public QObject, public ThumbCreator
+{
+ Q_OBJECT
+public:
+ VideoCreator();
+ virtual ~VideoCreator();
+ virtual bool create(const QString &path, int width, int height, QImage &img);
+ virtual Flags flags() const;
+
+private:
+ QPixmap m_sprocketSmall;
+ QPixmap m_sprocketMedium;
+ QPixmap m_sprocketLarge;
+};
+
+#endif
diff --git a/xine_artsplugin/tools/thumbnail/videoscaler.cpp b/xine_artsplugin/tools/thumbnail/videoscaler.cpp
new file mode 100644
index 00000000..2b4b49ad
--- /dev/null
+++ b/xine_artsplugin/tools/thumbnail/videoscaler.cpp
@@ -0,0 +1,258 @@
+/*
+ This file is part of KDE/aRts - xine integration
+ Copyright (C) 2003 Ewald Snel <ewald@rambo.its.tudelft.nl>
+
+ 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.
+
+ inspired by code from the xine project
+
+ Copyright (C) 2003 the xine project
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+
+#include <config.h>
+
+#ifdef HAVE_ALLOCA_H
+#include <alloca.h>
+#endif
+
+#include "videoscaler.h"
+
+#define THUMBNAIL_BRIGHTNESS +32
+#define THUMBNAIL_CONTRAST 128
+#define THUMBNAIL_SATURATION 128
+
+
+// Colorspace conversion tables
+static int tableLY[256];
+static int tableRV[256], tableBU[256], tableGU[256], tableGV[256];
+static int clipR[2240], clipG[2240], clipB[2240];
+
+static pthread_once_t once_control = PTHREAD_ONCE_INIT;
+
+
+static void init_once_routine()
+{
+ int cly = ( 76309 * THUMBNAIL_CONTRAST + 64) / 128;
+ int crv = (104597 * THUMBNAIL_SATURATION + 64) / 128;
+ int cbu = (132201 * THUMBNAIL_SATURATION + 64) / 128;
+ int cgu = ( 25675 * THUMBNAIL_SATURATION + 64) / 128;
+ int cgv = ( 53279 * THUMBNAIL_SATURATION + 64) / 128;
+ int i;
+
+ for (i=0; i < 256; i++)
+ {
+ tableLY[i] = cly * (i + THUMBNAIL_BRIGHTNESS - 16) + (864 << 16) + 32768;
+ tableRV[i] = crv * (i - 128);
+ tableBU[i] = cbu * (i - 128);
+ tableGU[i] = cgu * (i - 128);
+ tableGV[i] = cgv * (i - 128);
+ }
+ for (i=0; i < 2240; i++)
+ {
+ int c = (i < 864) ? 0 : ((i > 1119) ? 255 : (i - 864));
+
+ clipR[i] = c << 16;
+ clipG[i] = c << 8;
+ clipB[i] = c;
+ }
+}
+
+static void yuvToRgb32( unsigned char *bufy, unsigned char *bufu, unsigned char *bufv,
+ unsigned int *pixels, int width )
+{
+ for (int i=0; i < width; i++)
+ {
+ int l = tableLY[bufy[i]];
+
+ pixels[i] = clipR[(l + tableRV[bufv[i]]) >> 16] |
+ clipG[(l - tableGU[bufu[i]] - tableGV[bufv[i]]) >> 16] |
+ clipB[(l + tableBU[bufu[i]]) >> 16];
+ }
+}
+
+static inline void scaleLine( unsigned char *src[2], int width,
+ unsigned char *dst, int scaledWidth,
+ int scale, int weight, int step, int offset )
+{
+ int a, b, c, d;
+ int x = (scale / 2) - 32768;
+ unsigned char *p0 = (src[0] + offset);
+ unsigned char *p1 = (src[1] + offset);
+
+ weight >>= 8;
+
+ if (scaledWidth > width)
+ {
+ /* Trailing pixels, no horizontal filtering */
+ c = scaledWidth - (((width << 16) - 32768 - (scale / 2)) / scale);
+ a = p0[(step * width) - step];
+ b = p1[(step * width) - step];
+ a += (128 + (b - a) * weight) >> 8;
+ scaledWidth -= c;
+ memset( &dst[scaledWidth], a, c );
+
+ /* Leading pixels, no horizontal filtering */
+ c = (32767 + (scale / 2)) / scale;
+ a = p0[0];
+ b = p1[0];
+ a += (128 + (b - a) * weight) >> 8;
+ scaledWidth -= c;
+ memset( dst, a, c );
+
+ /* Adjust source and destination */
+ dst += c;
+ x += (c * scale);
+ }
+
+ for (int i=0; i < scaledWidth; i++)
+ {
+ int xhi = (step == 1) ? (x >> 16)
+ : ((step == 2) ? (x >> 15) & ~0x1
+ : (x >> 14) & ~0x3);
+ int xlo = (x & 0xFFFF) >> 8;
+
+ /* Four nearest points for bilinear filtering */
+ a = p0[xhi];
+ b = p0[xhi + step];
+ c = p1[xhi];\
+ d = p1[xhi + step];
+
+ /* Interpolate horizontally */
+ a = (256 * a) + (b - a) * xlo;
+ c = (256 * c) + (d - c) * xlo;
+
+ /* Interpolate vertically and store bilinear filtered sample */
+ *(dst++) = ((256 * a) + (c - a) * weight + 32768) >> 16;
+
+ x += scale;
+ }
+}
+
+void scaleYuvToRgb32( int width, int height,
+ unsigned char *base[3], unsigned int pitches[3],
+ int scaledWidth, int scaledHeight,
+ unsigned int *pixels, unsigned int bytesPerLine )
+{
+ int chromaWidth = (width + 1) / 2;
+ int chromaHeight = (height + 1) / 2;
+ int scaleX = (width << 16) / scaledWidth;
+ int scaleY = (height << 16) / scaledHeight;
+ int scaleCX = (scaleX / 2);
+ int y = (scaleY / 2) - 32768;
+
+ // Temporary line buffers (stack)
+ unsigned char *bufy = (unsigned char *)alloca( scaledWidth );
+ unsigned char *bufu = (unsigned char *)alloca( scaledWidth );
+ unsigned char *bufv = (unsigned char *)alloca( scaledWidth );
+
+ pthread_once( &once_control, init_once_routine );
+
+ for (int i=0; i < scaledHeight; i++)
+ {
+ unsigned char *twoy[2], *twou[2], *twov[2];
+ int y2 = (y / 2) - 32768;
+
+ // Calculate luminance scanlines for bilinear filtered scaling
+ if (y < 0)
+ {
+ twoy[0] = twoy[1] = base[0];
+ }
+ else if (y >= ((height - 1) << 16))
+ {
+ twoy[0] = twoy[1] = base[0] + (height - 1) * pitches[0];
+ }
+ else
+ {
+ twoy[0] = base[0] + (y >> 16) * pitches[0];
+ twoy[1] = twoy[0] + pitches[0];
+ }
+
+ // Calculate chrominance scanlines for bilinear filtered scaling
+ if (y2 < 0)
+ {
+ twou[0] = twou[1] = base[1];
+ twov[0] = twov[1] = base[2];
+ }
+ else if (y2 >= ((chromaHeight - 1) << 16))
+ {
+ twou[0] = twou[1] = base[1] + (chromaHeight - 1) * pitches[1];
+ twov[0] = twov[1] = base[2] + (chromaHeight - 1) * pitches[2];
+ }
+ else
+ {
+ twou[0] = base[1] + (y2 >> 16) * pitches[1];
+ twov[0] = base[2] + (y2 >> 16) * pitches[2];
+ twou[1] = twou[0] + pitches[1];
+ twov[1] = twov[0] + pitches[2];
+ }
+
+ // Bilinear filtered scaling
+ scaleLine( twoy, width, bufy, scaledWidth, scaleX, (y & 0xFFFF), 1, 0 );
+ scaleLine( twou, chromaWidth, bufu, scaledWidth, scaleCX, (y2 & 0xFFFF), 1, 0 );
+ scaleLine( twov, chromaWidth, bufv, scaledWidth, scaleCX, (y2 & 0xFFFF), 1, 0 );
+
+ // YUV to RGB32 comnversion
+ yuvToRgb32( bufy, bufu, bufv, pixels, scaledWidth );
+
+ pixels = (unsigned int *)(((char *)pixels) + bytesPerLine);
+ y += scaleY;
+ }
+}
+
+void scaleYuy2ToRgb32( int width, int height,
+ unsigned char *base, unsigned int pitch,
+ int scaledWidth, int scaledHeight,
+ unsigned int *pixels, unsigned int bytesPerLine )
+{
+ int chromaWidth = (width + 1) / 2;
+ int scaleX = (width << 16) / scaledWidth;
+ int scaleY = (height << 16) / scaledHeight;
+ int scaleCX = (scaleX / 2);
+ int y = (scaleY / 2) - 32768;
+
+ // Temporary line buffers (stack)
+ unsigned char *bufy = (unsigned char *)alloca( scaledWidth );
+ unsigned char *bufu = (unsigned char *)alloca( scaledWidth );
+ unsigned char *bufv = (unsigned char *)alloca( scaledWidth );
+
+ pthread_once( &once_control, init_once_routine );
+
+ for (int i=0; i < scaledHeight; i++)
+ {
+ unsigned char *two[2];
+
+ // Calculate scanlines for bilinear filtered scaling
+ if (y < 0)
+ {
+ two[0] = two[1] = base;
+ }
+ else if (y >= ((height - 1) << 16))
+ {
+ two[0] = two[1] = base + (height - 1) * pitch;
+ }
+ else
+ {
+ two[0] = base + (y >> 16) * pitch;
+ two[1] = two[0] + pitch;
+ }
+
+ // Bilinear filtered scaling
+ scaleLine( two, width, bufy, scaledWidth, scaleX, (y & 0xFFFF), 2, 0 );
+ scaleLine( two, chromaWidth, bufu, scaledWidth, scaleCX, (y & 0xFFFF), 4, 1 );
+ scaleLine( two, chromaWidth, bufv, scaledWidth, scaleCX, (y & 0xFFFF), 4, 3 );
+
+ // YUV to RGB32 comnversion
+ yuvToRgb32( bufy, bufu, bufv, pixels, scaledWidth );
+
+ pixels = (unsigned int *)(((char *)pixels) + bytesPerLine);
+ y += scaleY;
+ }
+}
diff --git a/xine_artsplugin/tools/thumbnail/videoscaler.h b/xine_artsplugin/tools/thumbnail/videoscaler.h
new file mode 100644
index 00000000..fd4d51db
--- /dev/null
+++ b/xine_artsplugin/tools/thumbnail/videoscaler.h
@@ -0,0 +1,24 @@
+/*
+ This file is part of KDE/aRts - xine integration
+ Copyright (C) 2003 Ewald Snel <ewald@rambo.its.tudelft.nl>
+
+ 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.
+*/
+
+#ifndef __VIDEOSCALER_H
+#define __VIDEOSCALER_H
+
+void scaleYuvToRgb32( int width, int height,
+ unsigned char *base[3], unsigned int pitches[3],
+ int scaledWidth, int scaledHeight,
+ unsigned int *pixels, unsigned int bytesPerLine );
+
+void scaleYuy2ToRgb32( int width, int height,
+ unsigned char *base, unsigned int pitch,
+ int scaledWidth, int scaledHeight,
+ unsigned int *pixels, unsigned int bytesPerLine );
+
+#endif
diff --git a/xine_artsplugin/tools/thumbnail/videothumbnail.desktop b/xine_artsplugin/tools/thumbnail/videothumbnail.desktop
new file mode 100644
index 00000000..e437b2a3
--- /dev/null
+++ b/xine_artsplugin/tools/thumbnail/videothumbnail.desktop
@@ -0,0 +1,71 @@
+[Desktop Entry]
+Type=Service
+Name=Video Files
+Name[af]=Video Lêers
+Name[ar]=ملفات مرئيات
+Name[bg]=Видео файлове
+Name[bn]=ভিডিও ফাইল
+Name[br]=Restroù Video
+Name[bs]=Video datoteke
+Name[ca]=Fitxers de vídeo
+Name[cs]=Video soubory
+Name[cy]=Ffeiliau Fideo
+Name[da]=Videofiler
+Name[de]=Video-Dateien
+Name[el]=Αρχεία βίντεο
+Name[eo]=Vidaj dosieroj
+Name[es]=Archivos de vídeo
+Name[et]=Videofailid
+Name[eu]=Bideo fitxategiak
+Name[fa]=پرونده‌های ویدیویی
+Name[fi]=Videotiedostot
+Name[fr]=Fichiers vidéo
+Name[ga]=Comhaid Fhíse
+Name[gl]=Ficheiros de Video
+Name[he]=קבצי וידאו
+Name[hi]=वीडियो फ़ाइलें
+Name[hr]=Video datoteke
+Name[hu]=Videófájlok
+Name[is]=Kvikmyndaskrár
+Name[it]=File Video
+Name[ja]=ビデオファイル
+Name[kk]=Бейне файлдар
+Name[km]=ឯកសារ​វីដេអូ
+Name[ko]=비디오 파일
+Name[lt]=Video bylos
+Name[mk]=Видео датотеки
+Name[nb]=Videofiler
+Name[nds]=Videodateien
+Name[ne]=भिडियो फाइल
+Name[nl]=Videobestanden
+Name[nn]=Videofiler
+Name[pa]=ਵੀਡਿਓ ਫਾਇਲਾਂ
+Name[pl]=Pliki wideo
+Name[pt]=Ficheiros de Vídeo
+Name[pt_BR]=Arquivos de vídeo
+Name[ro]=Fişiere video
+Name[ru]=Видеофайлы
+Name[sk]=Video súbory
+Name[sl]=Video datoteke
+Name[sr]=Видео фајлови
+Name[sr@Latn]=Video fajlovi
+Name[sv]=Videofiler
+Name[ta]=படக்காட்சி கோப்புகள்
+Name[tg]=Файлҳои Видео
+Name[th]=แฟ้มวิดีโอ
+Name[tr]=Video Dosyaları
+Name[uk]=Відеофайли
+Name[uz]=Video fayllar
+Name[uz@cyrillic]=Видео файллар
+Name[ven]=Dzifaela dza Video
+Name[wa]=Fitchîs videyo
+Name[xh]=Iifayile ze Video
+Name[zh_CN]=视频文件
+Name[zh_HK]=視訊檔案
+Name[zh_TW]=視訊檔案
+Name[zu]=Amafayela Evidiyo
+ServiceTypes=ThumbCreator
+MimeTypes=video/*,application/vnd.ms-asf,application/vnd.rn-realmedia
+X-KDE-Library=videothumbnail
+CacheThumbnail=true
+IgnoreMaximumSize=true