VLCThumbnailer.cpp 8.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*****************************************************************************
 * Media Library
 *****************************************************************************
 * Copyright (C) 2015 Hugo Beauzée-Luyssen, Videolabs
 *
 * Authors: Hugo Beauzée-Luyssen<hugo@beauzee.fr>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

23 24
#ifdef HAVE_CONFIG_H
#include "config.h"
25
#endif
26 27

#include "VLCThumbnailer.h"
28

29 30
#include "AlbumTrack.h"
#include "Album.h"
31
#include "Artist.h"
32 33
#include "Media.h"
#include "File.h"
34
#include "logging/Logger.h"
35
#include "MediaLibrary.h"
36
#include "utils/VLCInstance.h"
37
#include "utils/ModificationsNotifier.h"
38
#include "metadata_services/vlc/Common.hpp"
39

40 41 42 43 44 45 46 47
#ifdef HAVE_JPEG
#include "imagecompressors/JpegCompressor.h"
#elif defined(HAVE_EVAS)
#include "imagecompressors/EvasCompressor.h"
#else
#error No image compressor available
#endif

48 49 50
namespace medialibrary
{

51 52 53
VLCThumbnailer::VLCThumbnailer( MediaLibraryPtr ml )
    : m_ml( ml )
    , m_run( false )
54
    , m_prevSize( 0 )
55
{
56 57 58 59
#ifdef HAVE_JPEG
    m_compressor.reset( new JpegCompressor );
#elif defined(HAVE_EVAS)
    m_compressor.reset( new EvasCompressor );
60
#endif
61 62
}

63
VLCThumbnailer::~VLCThumbnailer()
64
{
65
    stop();
66 67
}

68
void VLCThumbnailer::requestThumbnail( MediaPtr media )
69
{
70 71 72 73 74 75 76 77 78 79
    {
        std::unique_lock<compat::Mutex> lock( m_mutex );
        auto task = std::unique_ptr<Task>( new Task( std::move( media ) ) );
        m_tasks.push( std::move( task ) );
    }
    if ( m_thread.get_id() == compat::Thread::id{} )
    {
        m_run = true;
        m_thread = compat::Thread( &VLCThumbnailer::run, this );
    }
80 81
}

82
void VLCThumbnailer::run()
83
{
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
    LOG_INFO( "Starting thumbnailer thread" );
    while ( m_run == true )
    {
        std::unique_ptr<Task> task;
        {
            std::unique_lock<compat::Mutex> lock( m_mutex );
            if ( m_tasks.size() == 0 )
            {
                m_cond.wait( lock, [this]() { return m_tasks.size() > 0 || m_run == false; } );
                if ( m_run == false )
                    break;
            }
            task = std::move( m_tasks.front() );
            m_tasks.pop();
        }
        bool res = generateThumbnail( *task );
        m_ml->getCb()->onMediaThumbnailReady( task->media, res );
    }
    LOG_INFO( "Exiting thumbnailer thread" );
}
104

105 106 107 108 109 110 111 112 113 114 115 116 117 118
void VLCThumbnailer::stop()
{
    bool running = true;
    if ( m_run.compare_exchange_strong( running, false ) )
    {
        {
            std::unique_lock<compat::Mutex> lock( m_mutex );
            while ( m_tasks.empty() == false )
                m_tasks.pop();
        }
        m_cond.notify_all();
        m_thread.join();
    }
}
119

120 121 122
bool VLCThumbnailer::generateThumbnail( Task& task )
{
    assert( task.media->type() != Media::Type::Audio );
123

124 125
    auto files = task.media->files();
    if ( files.empty() == true )
126
    {
127 128 129
        LOG_WARN( "Can't generate thumbnail for a media without associated files (",
                  task.media->title() );
        return false;
130
    }
131 132 133
    task.mrl = files[0]->mrl();

    LOG_INFO( "Generating ", task.mrl, " thumbnail..." );
134

135 136 137 138 139 140 141 142 143 144
    VLC::Media vlcMedia = VLC::Media( VLCInstance::get(), task.mrl,
                                      VLC::Media::FromType::FromLocation );

    vlcMedia.addOption( ":no-audio" );
    vlcMedia.addOption( ":no-osd" );
    vlcMedia.addOption( ":no-spu" );
    vlcMedia.addOption( ":input-fast-seek" );
    vlcMedia.addOption( ":avcodec-hw=none" );
    vlcMedia.addOption( ":no-mkv-preload-local-dir" );
    auto duration = vlcMedia.duration();
145
    if ( duration > 0 )
146 147 148 149
    {
        std::ostringstream ss;
        // Duration is in ms, start-time in seconds, and we're aiming at 1/4th of the media
        ss << ":start-time=" << duration / 4000;
150
        vlcMedia.addOption( ss.str() );
151 152
    }

153
    task.mp = VLC::MediaPlayer( vlcMedia );
154

155
    setupVout( task );
156

157 158
    auto res = MetadataCommon::startPlayback( vlcMedia, task.mp );
    if ( res == false )
159
    {
160 161
        LOG_WARN( "Failed to generate ", task.mrl, " thumbnail: Can't start playback" );
        return false;
162
    }
163

164
    if ( duration <= 0 )
165
    {
166
        // Seek ahead to have a significant preview
167 168
        res = seekAhead( task );
        if ( res == false )
169
        {
170 171
            LOG_WARN( "Failed to generate ", task.mrl, " thumbnail: Failed to seek ahead" );
            return false;
172
        }
173
    }
174 175 176 177 178 179 180 181 182
    res = takeThumbnail( task );
    if ( res == false )
        return false;

    LOG_INFO( "Done generating ", task.mrl, " thumbnail" );

    m_ml->getNotifier()->notifyMediaModification( task.media );

    return true;
183 184
}

185
bool VLCThumbnailer::seekAhead( Task& task )
186 187
{
    float pos = .0f;
188 189
    auto event = task.mp.eventManager().onPositionChanged([&task, &pos](float p) {
        std::unique_lock<compat::Mutex> lock( task.mutex );
190
        pos = p;
191
        task.cond.notify_all();
192
    });
193 194
    auto success = false;
    {
195 196 197
        std::unique_lock<compat::Mutex> lock( task.mutex );
        task.mp.setPosition( .4f );
        success = task.cond.wait_for( lock, std::chrono::seconds( 3 ), [&pos]() {
198 199 200
            return pos >= .1f;
        });
    }
201 202
    // Since we're locking a mutex for each position changed, let's unregister ASAP
    event->unregister();
203
    return success;
204 205
}

206
void VLCThumbnailer::setupVout( Task& task )
207
{
208
    task.mp.setVideoFormatCallbacks(
209
        // Setup
210 211
        [this, &task](char* chroma, unsigned int* width, unsigned int *height,
                    unsigned int *pitches, unsigned int *lines) {
212
            strcpy( chroma, m_compressor->fourCC() );
213 214 215

            const float inputAR = (float)*width / *height;

216 217 218
            task.width = DesiredWidth;
            task.height = (float)task.width / inputAR + 1;
            if ( task.height < DesiredHeight )
219 220
            {
                // Avoid downscaling too much for really wide pictures
221 222
                task.width = inputAR * DesiredHeight;
                task.height = DesiredHeight;
223
            }
224
            auto size = task.width * task.height * m_compressor->bpp();
225
            // If our buffer isn't enough anymore, reallocate a new one.
226
            if ( size > m_prevSize )
227
            {
228 229
                m_buff.reset( new uint8_t[size] );
                m_prevSize = size;
230
            }
231 232 233 234
            *width = task.width;
            *height = task.height;
            *pitches = task.width * m_compressor->bpp();
            *lines = task.height;
235 236 237 238
            return 1;
        },
        // Cleanup
        nullptr);
239
    task.mp.setVideoCallbacks(
240 241 242 243 244 245
        // Lock
        [this](void** pp_buff) {
            *pp_buff = m_buff.get();
            return nullptr;
        },
        //unlock
246 247 248
        nullptr,

        //display
249
        [this, &task](void*) {
250
            bool expected = true;
251
            if ( task.thumbnailRequired.compare_exchange_strong( expected, false ) )
252
            {
253
                task.cond.notify_all();
254 255 256 257 258
            }
        }
    );
}

259
bool VLCThumbnailer::takeThumbnail( Task& task )
260
{
261
    // lock, signal that we want a thumbnail, and wait.
262
    {
263 264 265
        std::unique_lock<compat::Mutex> lock( task.mutex );
        task.thumbnailRequired = true;
        bool success = task.cond.wait_for( lock, std::chrono::seconds( 15 ), [&task]() {
266
            // Keep waiting if the vmem thread hasn't restored m_thumbnailRequired to false
267
            return task.thumbnailRequired == false;
268 269
        });
        if ( success == false )
270
        {
271 272
            LOG_WARN( "Timed out while computing ", task.mrl, " snapshot" );
            return false;
273
        }
274
    }
275 276
    task.mp.stop();
    return compress( task );
277 278
}

279
bool VLCThumbnailer::compress( Task& task )
280
{
281
    auto path = m_ml->thumbnailPath();
282
    path += "/";
283
    path += std::to_string( task.media->id() ) + "." + m_compressor->extension();
284

285 286
    auto hOffset = task.width > DesiredWidth ? ( task.width - DesiredWidth ) / 2 : 0;
    auto vOffset = task.height > DesiredHeight ? ( task.height - DesiredHeight ) / 2 : 0;
287

288 289 290
    if ( m_compressor->compress( m_buff.get(), path, task.width, task.height,
                                 DesiredWidth, DesiredHeight, hOffset, vOffset ) == false )
        return false;
291

292 293
    task.media->setThumbnail( path );
    return true;
294
}
295

296 297 298 299 300
VLCThumbnailer::Task::Task( MediaPtr m )
    : media( std::move( m ) )
    , width( 0 )
    , height( 0 )
    , thumbnailRequired( false )
301 302
{
}
303 304

}