/* This file is part of the KDE libraries Copyright (C) 2002 Simon MacMullen Copyright (C) 2003 Ewald Snel 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #define XINE_ENABLE_EXPERIMENTAL_FEATURES 1 #include #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"