MediaLibrary.cpp 11.7 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
#include <algorithm>
#include <functional>
25
26
#include "Album.h"
#include "AlbumTrack.h"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
27
#include "Artist.h"
28
#include "AudioTrack.h"
29
#include "discoverer/DiscovererWorker.h"
30
#include "Media.h"
31
#include "Folder.h"
32
#include "MediaLibrary.h"
33
#include "IMetadataService.h"
34
#include "Label.h"
35
#include "logging/Logger.h"
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
36
#include "Movie.h"
37
#include "Parser.h"
38
39
#include "Show.h"
#include "ShowEpisode.h"
40
#include "database/SqliteTools.h"
41
#include "database/SqliteConnection.h"
42
#include "Utils.h"
43
#include "VideoTrack.h"
44

45
46
47
// Discoverers:
#include "discoverer/FsDiscoverer.h"

48
49
50
51
// Metadata services:
#include "metadata_services/vlc/VLCMetadataService.h"
#include "metadata_services/vlc/VLCThumbnailer.h"

52
53
#include "filesystem/IDirectory.h"
#include "filesystem/IFile.h"
54
55
#include "factory/FileSystem.h"

56
const std::vector<std::string> MediaLibrary::supportedVideoExtensions {
57
58
59
60
61
    // Videos
    "avi", "3gp", "amv", "asf", "divx", "dv", "flv", "gxf",
    "iso", "m1v", "m2v", "m2t", "m2ts", "m4v", "mkv", "mov",
    "mp2", "mp4", "mpeg", "mpeg1", "mpeg2", "mpeg4", "mpg",
    "mts", "mxf", "nsv", "nuv", "ogg", "ogm", "ogv", "ogx", "ps",
62
63
64
65
    "rec", "rm", "rmvb", "tod", "ts", "vob", "vro", "webm", "wmv"
};

const std::vector<std::string> MediaLibrary::supportedAudioExtensions {
66
67
68
69
70
71
72
73
    // Audio
    "a52", "aac", "ac3", "aiff", "amr", "aob", "ape",
    "dts", "flac", "it", "m4a", "m4p", "mid", "mka", "mlp",
    "mod", "mp1", "mp2", "mp3", "mpc", "oga", "ogg", "oma",
    "rmi", "s3m", "spx", "tta", "voc", "vqf", "w64", "wav",
    "wma", "wv", "xa", "xm"
};

74
MediaLibrary::MediaLibrary()
75
    : m_parser( new Parser )
76
    , m_discoverer( new DiscovererWorker )
77
78
79
{
}

80
81
MediaLibrary::~MediaLibrary()
{
82
83
84
85
86
    // The log callback isn't shared by all VLC::Instance's, yet since
    // they all share a single libvlc_instance_t, any VLC::Instance still alive
    // with a log callback set will try to invoke it.
    // We manually call logUnset to ensure that the callback that is about to be deleted will
    // not be called anymore.
87
88
    if ( m_vlcInstance.isValid() )
        m_vlcInstance.logUnset();
89
90
    // Explicitely stop the discoverer, to avoid it writting while tearing down.
    m_discoverer->stop();
91
    Media::clear();
92
    Folder::clear();
93
94
95
96
97
    Label::clear();
    Album::clear();
    AlbumTrack::clear();
    Show::clear();
    ShowEpisode::clear();
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
98
    Movie::clear();
99
    VideoTrack::clear();
100
    AudioTrack::clear();
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
101
    Artist::clear();
102
103
    // Explicitely release the connection's TLS
    m_dbConnection->release();
104
105
}

106
void MediaLibrary::setFsFactory(std::shared_ptr<factory::IFileSystem> fsFactory)
107
{
108
109
110
    m_fsFactory = fsFactory;
}

111
bool MediaLibrary::initialize( const std::string& dbPath, const std::string& snapshotPath, IMediaLibraryCb* mlCallback )
112
113
{
    if ( m_fsFactory == nullptr )
114
        m_fsFactory.reset( new factory::FileSystemDefaultFactory );
115
    m_snapshotPath = snapshotPath;
116
    m_callback = mlCallback;
117
    m_dbConnection.reset( new SqliteConnection( dbPath ) );
118

119
    if ( mlCallback != nullptr )
120
121
122
123
    {
        const char* args[] = {
            "-vv",
        };
124
125
        m_vlcInstance = VLC::Instance( sizeof(args) / sizeof(args[0]), args );
        m_vlcInstance.logSet([](int lvl, const libvlc_log_t*, std::string msg) {
126
127
128
129
130
131
132
133
            if ( lvl == LIBVLC_ERROR )
                Log::Error( msg );
            else if ( lvl == LIBVLC_WARNING )
                Log::Warning( msg );
            else
                Log::Info( msg );
        });

134
135
        auto vlcService = std::unique_ptr<VLCMetadataService>( new VLCMetadataService( m_vlcInstance ) );
        auto thumbnailerService = std::unique_ptr<VLCThumbnailer>( new VLCThumbnailer( m_vlcInstance ) );
136
137
138
139
        addMetadataService( std::move( vlcService ) );
        addMetadataService( std::move( thumbnailerService ) );
    }

140
    if ( ! ( Media::createTable( m_dbConnection.get() ) &&
141
142
143
144
145
146
147
148
149
150
        Folder::createTable( m_dbConnection.get() ) &&
        Label::createTable( m_dbConnection.get() ) &&
        Album::createTable( m_dbConnection.get() ) &&
        AlbumTrack::createTable( m_dbConnection.get() ) &&
        Show::createTable( m_dbConnection.get() ) &&
        ShowEpisode::createTable( m_dbConnection.get() ) &&
        Movie::createTable( m_dbConnection.get() ) &&
        VideoTrack::createTable( m_dbConnection.get() ) &&
        AudioTrack::createTable( m_dbConnection.get() ) &&
        Artist::createTable( m_dbConnection.get() ) ) )
151
    {
152
        LOG_ERROR( "Failed to create database structure" );
153
154
        return false;
    }
155
    m_discoverer->setCallback( m_callback );
156
    m_discoverer->addDiscoverer( std::unique_ptr<IDiscoverer>( new FsDiscoverer( m_fsFactory, this, m_dbConnection.get() ) ) );
157
    m_discoverer->reload();
158
    return true;
159
160
}

161
std::vector<MediaPtr> MediaLibrary::files()
162
{
163
    return Media::fetchAll( m_dbConnection.get() );
164
}
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
165

166
std::vector<MediaPtr> MediaLibrary::audioFiles()
167
{
168
    static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE type = ?";
169
    return Media::fetchAll( m_dbConnection.get(), req, IMedia::Type::AudioType );
170
171
}

172
std::vector<MediaPtr> MediaLibrary::videoFiles()
173
{
174
    static const std::string req = "SELECT * FROM " + policy::MediaTable::Name + " WHERE type = ?";
175
    return Media::fetchAll( m_dbConnection.get(), req, IMedia::Type::VideoType );
176
177
}

178
MediaPtr MediaLibrary::file( const std::string& path )
179
{
180
    return Media::fetch( m_dbConnection.get(), path );
181
182
}

183
std::shared_ptr<Media> MediaLibrary::addFile( const std::string& path, FolderPtr parentFolder )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
184
{
185
186
187
188
189
190
191
192
193
194
195
    std::unique_ptr<fs::IFile> file;
    try
    {
        file = m_fsFactory->createFile( path );
    }
    catch (std::exception& ex)
    {
        LOG_ERROR( "Failed to create an IFile for ", path, ": ", ex.what() );
        return nullptr;
    }

196
    auto type = IMedia::Type::UnknownType;
197
198
199
    if ( std::find( begin( supportedVideoExtensions ), end( supportedVideoExtensions ),
                    file->extension() ) != end( supportedVideoExtensions ) )
    {
200
        type = IMedia::Type::VideoType;
201
202
203
204
    }
    else if ( std::find( begin( supportedAudioExtensions ), end( supportedAudioExtensions ),
                         file->extension() ) != end( supportedAudioExtensions ) )
    {
205
        type = IMedia::Type::AudioType;
206
    }
207
    if ( type == IMedia::Type::UnknownType )
208
209
        return false;

210
    auto fptr = Media::create( m_dbConnection.get(), type, file.get(), parentFolder != nullptr ? parentFolder->id() : 0 );
211
212
213
214
215
216
217
218
    if ( fptr == nullptr )
    {
        LOG_ERROR( "Failed to add file ", file->fullPath(), " to the media library" );
        return nullptr;
    }
    LOG_INFO( "Adding ", file->name() );
    if ( m_callback != nullptr )
        m_callback->onFileAdded( fptr );
219
    m_parser->parse( fptr, m_callback );
220
    return fptr;
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
221
}
222

223
224
FolderPtr MediaLibrary::folder( const std::string& path )
{
225
    return Folder::fetch( m_dbConnection.get(), path );
226
227
}

228
229
bool MediaLibrary::deleteFile( const std::string& mrl )
{
230
    return Media::destroy( m_dbConnection.get(), mrl );
231
232
}

233
bool MediaLibrary::deleteFile( MediaPtr file )
234
{
235
    return Media::destroy( m_dbConnection.get(), std::static_pointer_cast<Media>( file ) );
236
237
}

238
239
bool MediaLibrary::deleteFolder( FolderPtr folder )
{
240
    if ( Folder::destroy( m_dbConnection.get(), std::static_pointer_cast<Folder>( folder ) ) == false )
241
        return false;
242
    Media::clear();
243
    return true;
244
245
}

246
247
LabelPtr MediaLibrary::createLabel( const std::string& label )
{
248
    return Label::create( m_dbConnection.get(), label );
249
}
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
250
251
252

bool MediaLibrary::deleteLabel( const std::string& text )
{
253
    return Label::destroy( m_dbConnection.get(), text );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
254
255
256
257
}

bool MediaLibrary::deleteLabel( LabelPtr label )
{
258
    return Label::destroy( m_dbConnection.get(), std::static_pointer_cast<Label>( label ) );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
259
}
260

261
AlbumPtr MediaLibrary::album(const std::string& title )
262
263
264
{
    // We can't use Cache helper, since albums are cached by primary keys
    static const std::string req = "SELECT * FROM " + policy::AlbumTable::Name +
265
            " WHERE title = ?";
266
    return Album::fetchOne( m_dbConnection.get(), req, title );
267
268
}

269
std::shared_ptr<Album> MediaLibrary::createAlbum(const std::string& title )
270
{
271
    return Album::create( m_dbConnection.get(), title );
272
273
}

274
275
std::vector<AlbumPtr> MediaLibrary::albums()
{
276
    return Album::fetchAll( m_dbConnection.get() );
277
278
}

279
280
281
282
ShowPtr MediaLibrary::show(const std::string& name)
{
    static const std::string req = "SELECT * FROM " + policy::ShowTable::Name
            + " WHERE name = ?";
283
    return Show::fetchOne( m_dbConnection.get(), req, name );
284
285
}

286
std::shared_ptr<Show> MediaLibrary::createShow(const std::string& name)
287
{
288
    return Show::create( m_dbConnection.get(), name );
289
290
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
291
292
293
294
MoviePtr MediaLibrary::movie( const std::string& title )
{
    static const std::string req = "SELECT * FROM " + policy::MovieTable::Name
            + " WHERE title = ?";
295
    return Movie::fetchOne( m_dbConnection.get(), req, title );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
296
297
}

298
std::shared_ptr<Movie> MediaLibrary::createMovie( const std::string& title )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
299
{
300
    return Movie::create( m_dbConnection.get(), title );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
301
302
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
303
304
305
306
ArtistPtr MediaLibrary::artist(const std::string &name)
{
    static const std::string req = "SELECT * FROM " + policy::ArtistTable::Name
            + " WHERE name = ?";
307
    return Artist::fetchOne( m_dbConnection.get(), req, name );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
308
309
}

310
311
312
313
ArtistPtr MediaLibrary::unknownArtist()
{
    if ( m_unknownArtist != nullptr )
        return m_unknownArtist;
314
    static const std::string req = "SELECT id_media FROM MediaArtistRelation "
315
316
317
318
319
320
321
322
            "WHERE id_artist IS NULL LIMIT 1";
    if ( sqlite::Tools::hasResults( m_dbConnection.get(), req ) == true )
    {
        m_unknownArtist = std::make_shared<Artist>( m_dbConnection.get() );
    }
    return m_unknownArtist;
}

323
std::shared_ptr<Artist> MediaLibrary::createArtist( const std::string& name )
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
324
{
325
    return Artist::create( m_dbConnection.get(), name );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
326
327
}

328
329
330
std::vector<ArtistPtr> MediaLibrary::artists() const
{
    static const std::string req = "SELECT * FROM " + policy::ArtistTable::Name;
331
    return Artist::fetchAll( m_dbConnection.get(), req );
332
333
}

Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
334
void MediaLibrary::addMetadataService(std::unique_ptr<IMetadataService> service)
335
{
336
337
338
339
340
    if ( service->initialize( m_parser.get(), this ) == false )
    {
        std::cout << "Failed to initialize service" << std::endl;
        return;
    }
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
341
    m_parser->addService( std::move( service ) );
342
}
343

344
345
void MediaLibrary::reload()
{
346
347
348
    // For the sake of simplicity, just flush the unknownArtist cache, regardless of any change
    // FIXME: Replicate this for "live" change handling, once we have it.
    m_unknownArtist = nullptr;
349
    m_discoverer->reload();
350
351
}

352
353
void MediaLibrary::discover( const std::string &entryPoint )
{
354
    m_discoverer->discover( entryPoint );
355
356
}

357
358
359
360
361
const std::string& MediaLibrary::snapshotPath() const
{
    return m_snapshotPath;
}

362
363
364
365
366
void MediaLibrary::setLogger( ILogger* logger )
{
    Log::SetLogger( logger );
}