You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdemultimedia/xine_artsplugin/tools/thumbnail/videocreator.cpp

377 lines
8.8 KiB

/* 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., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
// $Id$
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <pthread.h>
#include <sys/time.h>
#include <tqpixmap.h>
#include <tqdialog.h>
#include <tqfile.h>
#include <tqimage.h>
#include <tqpainter.h>
#include <tqpaintdevice.h>
#include <iostream>
#include <kstandarddirs.h>
#include <tdeapplication.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 TQImage 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));
TQImage 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 TQString &path, int width, int height, TQImage &img)
{
if (m_sprocketSmall.isNull())
{
TQString pixmap = locate( "data", "videothumbnail/sprocket-small.png" );
m_sprocketSmall = TQPixmap(pixmap);
pixmap = locate( "data", "videothumbnail/sprocket-medium.png" );
m_sprocketMedium = TQPixmap(pixmap);
pixmap = locate( "data", "videothumbnail/sprocket-large.png" );
m_sprocketLarge = TQPixmap(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, TQFile::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)
{
TQPixmap pix( createThumbnail( &frame, width, height ) );
TQPainter painter( &pix );
TQPixmap 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"