MetadataParser.cpp 32.8 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
25
26
#if HAVE_CONFIG_H
# include "config.h"
#endif

27
28
29
30
31
#include "MetadataParser.h"
#include "Album.h"
#include "AlbumTrack.h"
#include "Artist.h"
#include "File.h"
32
33
#include "medialibrary/filesystem/IDevice.h"
#include "medialibrary/filesystem/IDirectory.h"
34
#include "Folder.h"
35
#include "Genre.h"
36
#include "Media.h"
37
#include "Playlist.h"
38
#include "Show.h"
39
#include "utils/Directory.h"
40
#include "utils/Filename.h"
41
#include "utils/Url.h"
42
#include "utils/ModificationsNotifier.h"
43
44
45
#include "discoverer/FsDiscoverer.h"
#include "discoverer/probe/PathProbe.h"

46
#include <cstdlib>
47

48
49
50
namespace medialibrary
{

51
MetadataParser::MetadataParser()
52
53
    : m_ml( nullptr )
    , m_previousFolderId( 0 )
54
55
56
{
}

57
bool MetadataParser::cacheUnknownArtist()
58
{
59
    m_unknownArtist = Artist::fetch( m_ml, UnknownArtistID );
60
61
62
63
64
    if ( m_unknownArtist == nullptr )
        LOG_ERROR( "Failed to cache unknown artist" );
    return m_unknownArtist != nullptr;
}

65
bool MetadataParser::initialize( IMediaLibrary* ml )
66
{
67
68
    m_ml = static_cast<MediaLibrary*>( ml );
    m_notifier = m_ml->getNotifier();
69
    return cacheUnknownArtist();
70
71
}

72
int MetadataParser::toInt( parser::IItem& item, parser::IItem::Metadata meta )
73
{
74
    auto str = item.meta( meta );
75
76
77
78
79
80
81
82
    if ( str.empty() == false )
    {
        try
        {
            return std::stoi( str );
        }
        catch( std::logic_error& ex)
        {
83
            LOG_WARN( "Invalid meta #",
84
                      static_cast<typename std::underlying_type<parser::IItem::Metadata>::type>( meta ),
85
                      " provided (", str, "): ", ex.what() );
86
87
88
89
90
        }
    }
    return 0;
}

91
parser::Status MetadataParser::run( parser::IItem& item )
92
{
93
    bool alreadyInParser = false;
94
    int nbSubitem = item.nbSubItems();
95
96
97
    // Assume that file containing subitem(s) is a Playlist
    if ( nbSubitem > 0 )
    {
98
        auto res = addPlaylistMedias( item );
99
        if ( res == false ) // playlist addition may fail due to constraint violation
100
            return parser::Status::Fatal;
101

102
        assert( item.file() != nullptr );
103
        return parser::Status::Completed;
104
105
    }

106
    if ( item.file() == nullptr )
107
    {
108
        assert( item.media() == nullptr );
109
        // Try to create Media & File
110
        auto mrl = item.mrl();
111
        try
112
        {
113
            auto t = m_ml->getConn()->newTransaction();
114
            LOG_INFO( "Adding ", mrl );
115
116
            auto m = Media::create( m_ml, IMedia::Type::Unknown, utils::file::fileName( mrl ) );
            if ( m == nullptr )
117
            {
118
                LOG_ERROR( "Failed to add media ", mrl, " to the media library" );
119
                return parser::Status::Fatal;
120
121
            }
            // For now, assume all media are made of a single file
122
123
124
            auto file = m->addFile( *item.fileFs(),
                                    item.parentFolder()->id(),
                                    item.parentFolderFs()->device()->isRemovable(),
125
126
                                    File::Type::Main );
            if ( file == nullptr )
127
            {
128
                LOG_ERROR( "Failed to add file ", mrl, " to media #", m->id() );
129
                return parser::Status::Fatal;
130
            }
131
            item.setMedia( std::move( m ) );
132
133
            // Will invoke ITaskCb::updateFileId to upadte m_fileId & its
            // representation in DB
134
            item.setFile( std::move( file ) );
135
            t->commit();
136
        }
137
138
        // Voluntarily trigger an exception for a valid, but less common case, to avoid database overhead
        catch ( sqlite::errors::ConstraintViolation& ex )
139
        {
140
141
142
            LOG_INFO( "Creation of Media & File failed because ", ex.what(),
                      ". Assuming this task is a duplicate" );
            // Try to retrieve file & Media from database
143
            auto fileInDB = File::fromMrl( m_ml, mrl );
144
145
            if ( fileInDB == nullptr ) // The file is no longer present in DB, gracefully delete task
            {
146
                LOG_ERROR( "File ", mrl, " no longer present in DB, aborting");
147
                return parser::Status::Fatal;
148
            }
149
150
            auto media = fileInDB->media();
            if ( media == nullptr ) // Without a media, we cannot go further
151
                return parser::Status::Fatal;
152
153
            item.setFile( std::move( fileInDB ) );
            item.setMedia( std::move( media ) );
154

155
            alreadyInParser = true;
156
        }
157
    }
158
    else if ( item.media() == nullptr )
159
160
161
162
163
164
    {
        // If we have a file but no media, this is a problem, we can analyze as
        // much as we want, but won't be able to store anything.
        // Keep in mind that if we are in this code path, we are not analyzing
        // a playlist.
        assert( false );
165
        return parser::Status::Fatal;
166
    }
167
    auto media = std::static_pointer_cast<Media>( item.media() );
168

169
    if ( item.parentPlaylist() != nullptr )
170
        item.parentPlaylist()->add( media->id(), item.parentPlaylistIndex() );
171
172

    if ( alreadyInParser == true )
173
        return parser::Status::Discarded;
174

175
    const auto& tracks = item.tracks();
176
177

    if ( tracks.empty() == true )
178
        return parser::Status::Fatal;
179

180
    bool isAudio = true;
181
    {
182
        using TracksT = decltype( tracks );
183
        sqlite::Tools::withRetries( 3, [this, &isAudio, &item, &media]( TracksT tracks ) {
184
            auto t = m_ml->getConn()->newTransaction();
185
            for ( const auto& track : tracks )
186
            {
187
                if ( track.type == parser::IItem::Track::Type::Video )
188
                {
189
                    media->addVideoTrack( track.codec, track.v.width, track.v.height,
190
191
192
                                          static_cast<float>( track.v.fpsNum ) /
                                              static_cast<float>( track.v.fpsDen ),
                                          track.language, track.description );
193
194
                    isAudio = false;
                }
195
                else
196
                {
197
                    assert( track.type == parser::IItem::Track::Type::Audio );
198
                    media->addAudioTrack( track.codec, track.bitrate,
199
200
                                               track.a.rate, track.a.nbChannels,
                                               track.language, track.description );
201
                }
202
            }
203
            media->setDuration( item.duration() );
204
205
            t->commit();
        }, std::move( tracks ) );
206
207
208
    }
    if ( isAudio == true )
    {
209
        if ( parseAudioFile( item ) == false )
210
            return parser::Status::Fatal;
211
212
213
    }
    else
    {
214
        if (parseVideoFile( item ) == false )
215
            return parser::Status::Fatal;
216
    }
217

218
219
    if ( std::static_pointer_cast<File>( item.file() )->isDeleted() == true ||
         std::static_pointer_cast<Media>( media )->isDeleted() == true )
220
        return parser::Status::Fatal;
221

222
    m_notifier->notifyMediaCreation( media );
223
    return parser::Status::Success;
224
225
}

226
227
/* Playlist files */

228
bool MetadataParser::addPlaylistMedias( parser::IItem& item ) const
229
{
230
    const auto& mrl = item.mrl();
231
    LOG_INFO( "Try to import ", mrl, " as a playlist" );
232
    std::shared_ptr<Playlist> playlistPtr;
233
    if ( item.file() != nullptr )
234
    {
235
236
237
238
239
        // We are most likely re-scanning a file representing a playlist.
        // If a task has a file, it means the playlist & the associated file have
        // been created.
        std::string req = "SELECT * FROM " + policy::PlaylistTable::Name +
                " WHERE file_id = ?";
240
        playlistPtr = Playlist::fetch( m_ml, req, item.file()->id() );
241
242
243
244
245
246
247
        if ( playlistPtr == nullptr )
        {
            // The playlist had to be created, something is very wrong, give up
            // FIXME: Check that the task will be deleted.
            assert( false );
            return false;
        }
248
    }
249
    else
250
    {
251
        auto playlistName = item.meta( parser::IItem::Metadata::Title );
252
253
254
255
256
257
258
259
260
261
        if ( playlistName.empty() == true )
            playlistName = utils::url::decode( utils::file::fileName( mrl ) );
        auto t = m_ml->getConn()->newTransaction();
        playlistPtr = Playlist::create( m_ml, playlistName );
        if ( playlistPtr == nullptr )
        {
            LOG_ERROR( "Failed to create playlist ", mrl, " to the media library" );
            return false;
        }

262
263
264
        auto file = playlistPtr->addFile( *item.fileFs(),
                                          item.parentFolder()->id(),
                                          item.parentFolderFs()->device()->isRemovable() );
265
266
267
268
269
        if ( file == nullptr )
        {
            LOG_ERROR( "Failed to add playlist file ", mrl );
            return false;
        }
270
271
        // Will invoke ITaskCb::updateFileId to upadte m_fileId & its
        // representation in DB
272
        item.setFile( std::move( file ) );
273
        t->commit();
274
    }
275
276
277
278
279
    // Now regardless of if the playlist is re-scanned or discovered from the
    // first time, just schedule all members for insertion. media & files will
    // be recreated if need be, and appropriate entries in PlaylistMediaRelation
    // table will be recreated to link things together.

280
281
    for ( auto i = 0u; i < item.nbSubItems(); ++i ) // FIXME: Interrupt loop if paused
        addPlaylistElement( item, playlistPtr, item.subItem( i ) );
282
283
284
285

    return true;
}

286
void MetadataParser::addPlaylistElement( parser::IItem& item,
287
                                         std::shared_ptr<Playlist> playlistPtr,
288
                                         const parser::IItem& subitem ) const
289
{
290
    const auto& mrl = subitem.mrl();
291
    LOG_INFO( "Try to add ", mrl, " to the playlist ", mrl );
292
293
294
    auto media = m_ml->media( mrl );
    if ( media != nullptr )
    {
295
        LOG_INFO( "Media for ", mrl, " already exists, adding it to the playlist ", mrl );
296
        playlistPtr->add( media->id(), subitem.parentPlaylistIndex() );
297
298
299
300
301
302
303
304
305
        return;
    }
    // Create Media, etc.
    auto fsFactory = m_ml->fsFactoryForMrl( mrl );

    if ( fsFactory == nullptr ) // Media not supported by any FsFactory, registering it as external
    {
        auto t2 = m_ml->getConn()->newTransaction();
        auto externalMedia = Media::create( m_ml, IMedia::Type::Unknown, utils::url::encode(
306
                subitem.meta( parser::IItem::Metadata::Title ) ) );
307
308
        if ( externalMedia == nullptr )
        {
309
            LOG_ERROR( "Failed to create external media for ", mrl, " in the playlist ", item.mrl() );
310
311
312
313
314
            return;
        }
        // Assuming that external mrl present in playlist file is a main media resource
        auto externalFile = externalMedia->addExternalMrl( mrl, IFile::Type::Main );
        if ( externalFile == nullptr )
315
            LOG_ERROR( "Failed to create external file for ", mrl, " in the playlist ", item.mrl() );
316
        playlistPtr->add( externalMedia->id(), subitem.parentPlaylistIndex() );
317
318
319
320
321
322
323
324
325
326
327
328
329
        t2->commit();
        return;
    }
    bool isDirectory;
    try
    {
        isDirectory = utils::fs::isDirectory( utils::file::toLocalPath( mrl ) );
    }
    catch ( std::system_error& ex )
    {
        LOG_ERROR( ex.what() );
        return;
    }
330
    LOG_INFO( "Importing ", isDirectory ? "folder " : "file ", mrl, " in the playlist ", item.mrl() );
331
332
333
334
    auto directoryMrl = utils::file::directory( mrl );
    auto parentFolder = Folder::fromMrl( m_ml, directoryMrl );
    bool parentKnown = parentFolder != nullptr;

335
336
337
338
339
340
341
342
    // The minimal entrypoint must be a device mountpoint
    auto device = fsFactory->createDeviceFromMrl( mrl );
    if ( device == nullptr )
    {
        LOG_ERROR( "Can't add a local folder with unknown storage device. ");
        return;
    }
    auto entryPoint = device->mountpoint();
343
344
    if ( parentKnown == false && Folder::fromMrl( m_ml, entryPoint ) != nullptr )
    {
345
346
347
348
        auto probePtr = std::unique_ptr<prober::PathProbe>(
                    new prober::PathProbe{ utils::file::stripScheme( mrl ),
                       isDirectory, std::move( playlistPtr ), parentFolder,
                       utils::file::stripScheme( directoryMrl ), subitem.parentPlaylistIndex(), true } );
349
        FsDiscoverer discoverer( fsFactory, m_ml, nullptr, std::move( probePtr ) );
350
351
352
        discoverer.reload( entryPoint );
        return;
    }
353
354
355
356
    auto probePtr = std::unique_ptr<prober::PathProbe>(
                new prober::PathProbe{ utils::file::stripScheme( mrl ),
                   isDirectory, std::move( playlistPtr ), parentFolder,
                   utils::file::stripScheme( directoryMrl ), subitem.parentPlaylistIndex(), false } );
357
    FsDiscoverer discoverer( fsFactory, m_ml, nullptr, std::move( probePtr ) );
358
359
360
    if ( parentKnown == false )
    {
        discoverer.discover( entryPoint );
361
362
363
        auto entryFolder = Folder::fromMrl( m_ml, entryPoint );
        if ( entryFolder != nullptr )
            Folder::excludeEntryFolder( m_ml, entryFolder->id() );
364
365
366
367
368
        return;
    }
    discoverer.reload( directoryMrl );
}

369
370
/* Video files */

371
bool MetadataParser::parseVideoFile( parser::IItem& item ) const
372
{
373
    auto media = static_cast<Media*>( item.media().get() );
374
    media->setType( IMedia::Type::Video );
375
    const auto& title = item.meta( parser::IItem::Metadata::Title );
376
    if ( title.length() == 0 )
377
        return true;
378

379
380
    const auto& showName = item.meta( parser::IItem::Metadata::ShowName );
    const auto& artworkMrl = item.meta( parser::IItem::Metadata::ArtworkUrl );
381

382
    return sqlite::Tools::withRetries( 3, [this, &showName, &title, media, &item, &artworkMrl]() {
383
        auto t = m_ml->getConn()->newTransaction();
384
        media->setTitleBuffered( title );
385

386
        if ( artworkMrl.empty() == false )
387
            media->setThumbnail( artworkMrl, Thumbnail::Origin::Media );
388

389
390
        if ( showName.length() != 0 )
        {
391
            auto show = m_ml->show( showName );
392
            if ( show == nullptr )
393
394
395
396
397
            {
                show = m_ml->createShow( showName );
                if ( show == nullptr )
                    return false;
            }
398
            auto episode = toInt( item, parser::IItem::Metadata::Episode );
399
400
401
            if ( episode != 0 )
            {
                std::shared_ptr<Show> s = std::static_pointer_cast<Show>( show );
402
                s->addEpisode( *media, title, episode );
403
            }
404
405
406
407
408
        }
        else
        {
            // How do we know if it's a movie or a random video?
        }
409
        media->save();
410
411
412
        t->commit();
        return true;
    });
413
414
415
416
417
    return true;
}

/* Audio files */

418
bool MetadataParser::parseAudioFile( parser::IItem& item )
419
{
420
421
    auto media = static_cast<Media*>( item.media().get() );
    media->setType( IMedia::Type::Audio );
422

423
    auto artworkMrl = item.meta( parser::IItem::Metadata::ArtworkUrl );
424
    if ( artworkMrl.empty() == false )
425
    {
426
        media->setThumbnail( artworkMrl, Thumbnail::Origin::Media );
427
428
429
430
431
        // Don't use an attachment as default artwork for album/artists
        if ( utils::file::schemeIs( "attachment", artworkMrl ) )
            artworkMrl.clear();
    }

432

433
434
    auto genre = handleGenre( item );
    auto artists = findOrCreateArtist( item );
435
436
    if ( artists.first == nullptr && artists.second == nullptr )
        return false;
437
    auto album = findAlbum( item, artists.first, artists.second );
438
    return sqlite::Tools::withRetries( 3, [this, &item, &artists, media]( std::string artworkMrl,
439
440
                                                  std::shared_ptr<Album> album, std::shared_ptr<Genre> genre ) {
        auto t = m_ml->getConn()->newTransaction();
441
        if ( album == nullptr )
442
        {
443
            const auto& albumName = item.meta( parser::IItem::Metadata::Album );
444
445
446
447
448
449
450
451
452
            int64_t thumbnailId = 0;
            if ( artworkMrl.empty() == false )
            {
                auto thumbnail = Thumbnail::create( m_ml, artworkMrl,
                                                    Thumbnail::Origin::Album );
                if ( thumbnail != nullptr )
                    thumbnailId = thumbnail->id();
            }
            album = m_ml->createAlbum( albumName, thumbnailId );
453
454
455
456
457
            if ( album == nullptr )
                return false;
            m_notifier->notifyAlbumCreation( album );
        }
        // If we know a track artist, specify it, otherwise, fallback to the album/unknown artist
458
        auto track = handleTrack( album, item, artists.second ? artists.second : artists.first,
459
460
                                  genre.get() );

461
462
        auto res = link( *media, album, artists.first, artists.second );
        media->save();
463
464
465
        t->commit();
        return res;
    }, std::move( artworkMrl ), std::move( album ), std::move( genre ) );
466
467
}

468
std::shared_ptr<Genre> MetadataParser::handleGenre( parser::IItem& item ) const
469
{
470
    const auto& genreStr = item.meta( parser::IItem::Metadata::Genre );
471
    if ( genreStr.length() == 0 )
472
        return nullptr;
473
    auto genre = Genre::fromName( m_ml, genreStr );
474
475
    if ( genre == nullptr )
    {
476
        genre = Genre::create( m_ml, genreStr );
477
        if ( genre == nullptr )
478
            LOG_ERROR( "Failed to get/create Genre", genreStr );
479
480
481
482
    }
    return genre;
}

483
/* Album handling */
484

485
std::shared_ptr<Album> MetadataParser::findAlbum( parser::IItem& item, std::shared_ptr<Artist> albumArtist,
486
                                                    std::shared_ptr<Artist> trackArtist )
487
{
488
    const auto& albumName = item.meta( parser::IItem::Metadata::Album );
489
    if ( albumName.empty() == true )
490
    {
491
492
        if ( albumArtist != nullptr )
            return albumArtist->unknownAlbum();
493
        if ( trackArtist != nullptr )
494
495
            return trackArtist->unknownAlbum();
        return m_unknownArtist->unknownAlbum();
496
497
    }

498
    auto file = static_cast<File*>( item.file().get() );
499
    if ( m_previousAlbum != nullptr && albumName == m_previousAlbum->title() &&
500
         m_previousFolderId != 0 && file->folderId() == m_previousFolderId )
501
502
503
504
        return m_previousAlbum;
    m_previousAlbum.reset();
    m_previousFolderId = 0;

505
506
    // Album matching depends on the difference between artist & album artist.
    // Specificaly pass the albumArtist here.
507
508
    static const std::string req = "SELECT * FROM " + policy::AlbumTable::Name +
            " WHERE title = ?";
509
    auto albums = Album::fetchAll<Album>( m_ml, req, albumName );
510
511
512
513

    if ( albums.size() == 0 )
        return nullptr;

514
515
    const auto discTotal = toInt( item, parser::IItem::Metadata::DiscTotal );
    const auto discNumber = toInt( item, parser::IItem::Metadata::DiscNumber );
516
517
518
519
520
    /*
     * Even if we get only 1 album, we need to filter out invalid matches.
     * For instance, if we have already inserted an album "A" by an artist "john"
     * but we are now trying to handle an album "A" by an artist "doe", not filtering
     * candidates would yield the only "A" album we know, while we should return
521
     * nullptr, so the link() method can create a new one.
522
523
524
525
     */
    for ( auto it = begin( albums ); it != end( albums ); )
    {
        auto a = (*it).get();
526
        auto candidateAlbumArtist = a->albumArtist();
527
528
529
530
        // When we find an album, we will systematically assign an artist to it.
        // Not having an album artist (even it it's only a temporary one in the
        // case of a compilation album) is not expected at all.
        assert( candidateAlbumArtist != nullptr );
531
532
533
534
        if ( albumArtist != nullptr )
        {
            // We assume that an album without album artist is a positive match.
            // At the end of the day, without proper tags, there's only so much we can do.
535
            if ( candidateAlbumArtist->id() != albumArtist->id() )
536
537
538
539
540
541
542
543
544
545
546
            {
                it = albums.erase( it );
                continue;
            }
        }
        // If this is a multidisc album, assume it could be in a multiple amount of folders.
        // Since folders can come in any order, we can't assume the first album will be the
        // first media we see. If the discTotal or discNumber meta are provided, that's easy. If not,
        // we assume that another CD with the same name & artists, and a disc number > 1
        // denotes a multi disc album
        // Check the first case early to avoid fetching tracks if unrequired.
547
        if ( discTotal > 1 || discNumber > 1 )
548
549
550
551
        {
            ++it;
            continue;
        }
552
        const auto tracks = a->cachedTracks();
553
554
555
556
557
558
559
        // If there is no tracks to compare with, we just have to hope this will be the only valid
        // album match
        if ( tracks.size() == 0 )
        {
            ++it;
            continue;
        }
560
561

        auto multiDisc = false;
562
563
        auto multipleArtists = false;
        int64_t previousArtistId = trackArtist != nullptr ? trackArtist->id() : 0;
564
565
566
567
        for ( auto& t : tracks )
        {
            auto at = t->albumTrack();
            assert( at != nullptr );
568
569
570
            if ( at == nullptr )
                continue;
            if ( at->discNumber() > 1 )
571
                multiDisc = true;
572
573
574
575
576
            if ( previousArtistId != 0 && previousArtistId != at->artist()->id() )
                multipleArtists = true;
            previousArtistId = at->artist()->id();
            // We now know enough about the album, we can stop looking at its tracks
            if ( multiDisc == true && multipleArtists == true )
577
578
579
580
581
582
583
584
                break;
        }
        if ( multiDisc )
        {
            ++it;
            continue;
        }

585
        // Assume album files will be in the same folder.
586
        auto newFileFolder = utils::file::directory( file->mrl() );
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
        auto trackFiles = tracks[0]->files();
        bool differentFolder = false;
        for ( auto& f : trackFiles )
        {
            auto candidateFolder = utils::file::directory( f->mrl() );
            if ( candidateFolder != newFileFolder )
            {
                differentFolder = true;
                break;
            }
        }
        // We now have a candidate by the same artist in the same folder, assume it to be
        // a positive match.
        if ( differentFolder == false )
        {
            ++it;
            continue;
        }

606
607
608
609
        // Attempt to discriminate by date, but only for the same artists.
        // Not taking the artist in consideration would cause compilation to
        // create multiple albums, especially when track are only partially
        // tagged with a year.
610
        if ( multipleArtists == false )
611
        {
612
            auto candidateDate = item.meta( parser::IItem::Metadata::Date );
613
            if ( candidateDate.empty() == false )
614
            {
615
616
617
618
619
620
621
622
623
624
625
626
627
                try
                {
                    unsigned int year = std::stoi( candidateDate );
                    if ( year != a->releaseYear() )
                        it = albums.erase( it );
                    else
                        ++it;
                    continue;
                }
                catch (...)
                {
                    // Date wasn't helpful, simply ignore the error and continue
                }
628
629
            }
        }
630
631
632
633
634
635
636
637
638
        // The candidate is :
        // - in a different folder
        // - not a multidisc album
        // - Either:
        //      - from the same artist & without a date to discriminate
        //      - from the same artist & with a different date
        //      - from different artists
        // Assume it's a negative match.
        it = albums.erase( it );
639
640
641
642
643
    }
    if ( albums.size() == 0 )
        return nullptr;
    if ( albums.size() > 1 )
    {
644
        LOG_WARN( "Multiple candidates for album ", albumName, ". Selecting first one out of luck" );
645
    }
646
    m_previousFolderId = file->folderId();
647
648
    m_previousAlbum = albums[0];
    return albums[0];
649
650
651
652
}

///
/// \brief MetadataParser::handleArtists Returns Artist's involved on a track
653
/// \param task The current parser task
654
655
656
657
/// \return A pair containing:
/// The album artist as a first element
/// The track artist as a second element, or nullptr if it is the same as album artist
///
658
std::pair<std::shared_ptr<Artist>, std::shared_ptr<Artist>> MetadataParser::findOrCreateArtist( parser::IItem& item ) const
659
660
661
{
    std::shared_ptr<Artist> albumArtist;
    std::shared_ptr<Artist> artist;
662
    static const std::string req = "SELECT * FROM " + policy::ArtistTable::Name + " WHERE name = ?";
663

664
665
    const auto& albumArtistStr = item.meta( parser::IItem::Metadata::AlbumArtist );
    const auto& artistStr = item.meta( parser::IItem::Metadata::Artist );
666
    if ( albumArtistStr.empty() == true && artistStr.empty() == true )
667
    {
668
        return {m_unknownArtist, m_unknownArtist};
669
670
    }

671
    if ( albumArtistStr.empty() == false )
672
    {
673
        albumArtist = Artist::fetch( m_ml, req, albumArtistStr );
674
675
        if ( albumArtist == nullptr )
        {
676
            albumArtist = m_ml->createArtist( albumArtistStr );
677
678
            if ( albumArtist == nullptr )
            {
679
                LOG_ERROR( "Failed to create new artist ", albumArtistStr );
680
681
                return {nullptr, nullptr};
            }
682
            m_notifier->notifyArtistCreation( albumArtist );
683
684
        }
    }
685
    if ( artistStr.empty() == false && artistStr != albumArtistStr )
686
    {
687
        artist = Artist::fetch( m_ml, req, artistStr );
688
689
        if ( artist == nullptr )
        {
690
            artist = m_ml->createArtist( artistStr );
691
692
            if ( artist == nullptr )
            {
693
                LOG_ERROR( "Failed to create new artist ", artistStr );
694
695
                return {nullptr, nullptr};
            }
696
            m_notifier->notifyArtistCreation( artist );
697
698
699
700
701
702
703
        }
    }
    return {albumArtist, artist};
}

/* Tracks handling */

704
std::shared_ptr<AlbumTrack> MetadataParser::handleTrack( std::shared_ptr<Album> album, parser::IItem& item,
705
                                                         std::shared_ptr<Artist> artist, Genre* genre ) const
706
{
707
708
    assert( sqlite::Transaction::transactionInProgress() == true );

709
710
711
    auto title = item.meta( parser::IItem::Metadata::Title );
    const auto trackNumber = toInt( item, parser::IItem::Metadata::TrackNumber );
    const auto discNumber = toInt( item, parser::IItem::Metadata::DiscNumber );
712
    auto media = std::static_pointer_cast<Media>( item.media() );
713
714
715
    if ( title.empty() == true )
    {
        LOG_WARN( "Failed to get track title" );
716
        if ( trackNumber != 0 )
717
718
        {
            title = "Track #";
719
            title += std::to_string( trackNumber );
720
721
722
        }
    }
    if ( title.empty() == false )
723
        media->setTitleBuffered( title );
724

725
    auto track = std::static_pointer_cast<AlbumTrack>( album->addTrack( media, trackNumber,
726
                                                                        discNumber, artist->id(),
727
                                                                        genre ) );
728
729
730
731
732
    if ( track == nullptr )
    {
        LOG_ERROR( "Failed to create album track" );
        return nullptr;
    }
733

734
    const auto& releaseDate = item.meta( parser::IItem::Metadata::Date );
735
    if ( releaseDate.empty() == false )
736
    {
737
        auto releaseYear = atoi( releaseDate.c_str() );
738
        media->setReleaseDate( releaseYear );
739
740
741
742
743
        // Let the album handle multiple dates. In order to do this properly, we need
        // to know if the date has been changed before, which can be known only by
        // using Album class internals.
        album->setReleaseYear( releaseYear, false );
    }
744
    m_notifier->notifyAlbumTrackCreation( track );
745
746
747
748
749
750
    return track;
}

/* Misc */

bool MetadataParser::link( Media& media, std::shared_ptr<Album> album,
751
                               std::shared_ptr<Artist> albumArtist, std::shared_ptr<Artist> artist )
752
{
753
    if ( albumArtist == nullptr )
754
755
    {
        assert( artist != nullptr );
756
        albumArtist = artist;
757
    }
758
    assert( album != nullptr );
759

760
761
    auto albumThumbnail = album->thumbnail();

762
763
764
765
    // We might modify albumArtist later, hence handle thumbnails before.
    // If we have an albumArtist (meaning the track was properly tagged, we
    // can assume this artist is a correct match. We can use the thumbnail from
    // the current album for the albumArtist, if none has been set before.
766
767
768
769
    // Although we don't want to do this for unknown/various artists, as the
    // thumbnail wouldn't reflect those "special" artists
    if ( albumArtist != nullptr && albumArtist->id() != UnknownArtistID &&
         albumArtist->id() != VariousArtistID &&
770
771
772
773
774
775
776
777
778
779
780
781
782
783
         albumThumbnail != nullptr )
    {
        auto albumArtistThumbnail = albumArtist->thumbnail();
        // If the album artist has no thumbnail, let's assign it
        if ( albumArtistThumbnail == nullptr )
        {
            albumArtist->setArtworkMrl( albumThumbnail->mrl(), Thumbnail::Origin::AlbumArtist );
        }
        else if ( albumArtistThumbnail->origin() == Thumbnail::Origin::Artist )
        {
            // We only want to change the thumbnail if it was assigned from an
            // album this artist was only featuring on
        }
    }
784

785
786
787
788
    // Until we have a better artwork extraction/assignation, simply do the same
    // for artists
    if ( artist != nullptr && artist->id() != UnknownArtistID &&
         artist->id() != VariousArtistID &&
789
790
791
792
         albumThumbnail != nullptr && artist->thumbnail() == nullptr )
    {
        artist->setArtworkMrl( album->artworkMrl(), Thumbnail::Origin::Artist );
    }
793

794
795
796
797
798
799
800
801
802
803
804
805
806
    if ( albumArtist != nullptr )
        albumArtist->addMedia( media );
    if ( artist != nullptr && ( albumArtist == nullptr || albumArtist->id() != artist->id() ) )
        artist->addMedia( media );

    auto currentAlbumArtist = album->albumArtist();

    // If we have no main artist yet, that's easy, we need to assign one.
    if ( currentAlbumArtist == nullptr )
    {
        // We don't know if the artist was tagged as artist or albumartist, however, we simply add it
        // as the albumartist until proven we were wrong (ie. until one of the next tracks
        // has a different artist)
807
        album->setAlbumArtist( albumArtist );
808
809
        // Always add the album artist as an artist
        album->addArtist( albumArtist );
810
811
812
        // Always update the album artist number of tracks.
        // The artist might be different, and will be handled a few lines below
        albumArtist->updateNbTrack( 1 );
813
        if ( artist != nullptr )
814
815
816
817
818
        {
            // If the album artist is not the artist, we need to update the
            // album artist track count as well.
            if ( albumArtist->id() != artist->id() )
                artist->updateNbTrack( 1 );
819
            album->addArtist( artist );
820
        }
821
822
823
    }
    else
    {
824
        // We have more than a single artist on this album, fallback to various artists
825
826
        if ( albumArtist->id() != currentAlbumArtist->id() )
        {
827
828
            if ( m_variousArtists == nullptr )
                m_variousArtists = Artist::fetch( m_ml, VariousArtistID );
829
830
831
832
833
834
835
836
837
838
839
840
841
842
            // If we already switched to various artist, no need to do it again
            if ( m_variousArtists->id() != currentAlbumArtist->id() )
            {
                // All tracks from this album must now also be reflected in various
                // artist number of tracks
                m_variousArtists->updateNbTrack( album->nbTracks() );
                album->setAlbumArtist( m_variousArtists );
            }
            // However we always need to bump the various artist number of tracks
            else
            {
                m_variousArtists->updateNbTrack( 1 );
            }
            // Add this artist as "featuring".
843
844
845
846
            album->addArtist( albumArtist );
        }
        if ( artist != nullptr && artist->id() != albumArtist->id() )
        {
847
848
           album->addArtist( artist );
           artist->updateNbTrack( 1 );
849
        }
850
        albumArtist->updateNbTrack( 1 );
851
852
853
854
855
856
857
858
859
    }

    return true;
}

const char* MetadataParser::name() const
{
    return "Metadata";
}
860
861
862
863
864
865
866
867
868
869

uint8_t MetadataParser::nbThreads() const
{
//    auto nbProcs = std::thread::hardware_concurrency();
//    if ( nbProcs == 0 )
//        return 1;
//    return nbProcs;
    // Let's make this code thread-safe first :)
    return 1;
}
870

871
void MetadataParser::onFlushing()
872
873
874
875
876
877
{
    m_variousArtists = nullptr;
    m_previousAlbum = nullptr;
    m_previousFolderId = 0;
}

878
void MetadataParser::onRestarted()
879
880
{
    // Reset locally cached entities
881
    cacheUnknownArtist();
882
883
}

884
parser::Step MetadataParser::targetedStep() const
885
{
886
    return parser::Step::MetadataAnalysis;
887
888
}

889
}