File indexing completed on 2024-05-19 04:49:29

0001 /****************************************************************************************
0002  * Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com>            *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #include "core/meta/support/MetaUtility.h"
0018 
0019 #include "core/capabilities/Capability.h"
0020 #include "core/meta/Meta.h"
0021 #include "core/meta/Statistics.h"
0022 #include "core/meta/TrackEditor.h"
0023 #include "core/support/Debug.h"
0024 
0025 #include <QTime>
0026 #include <QChar>
0027 #include <QFile>
0028 
0029 #include <KIO/Global>
0030 #include <KLocalizedString>
0031 
0032         static const QString XESAM_ALBUM          = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#album");
0033         static const QString XESAM_ALBUMARTIST    = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#albumArtist");
0034         static const QString XESAM_ARTIST         = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#artist");
0035         static const QString XESAM_BITRATE        = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#audioBitrate");
0036         static const QString XESAM_BPM            = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#audioBPM");
0037         static const QString XESAM_CODEC          = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#audioCodec");
0038         static const QString XESAM_COMMENT        = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#comment");
0039         static const QString XESAM_COMPOSER       = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#composer");
0040         static const QString XESAM_DISCNUMBER     = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#discNumber");
0041         static const QString XESAM_FILESIZE       = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#size");
0042         static const QString XESAM_GENRE          = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#genre");
0043         static const QString XESAM_LENGTH         = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#mediaDuration");
0044         static const QString XESAM_RATING         = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#userRating");
0045         static const QString XESAM_SAMPLERATE     = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#audioSampleRate");
0046         static const QString XESAM_TITLE          = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#title");
0047         static const QString XESAM_TRACKNUMBER    = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#trackNumber");
0048         static const QString XESAM_URL            = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#url");
0049         static const QString XESAM_YEAR           = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#contentCreated");
0050 
0051         static const QString XESAM_SCORE          = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#autoRating");
0052         static const QString XESAM_PLAYCOUNT      = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#useCount");
0053         static const QString XESAM_FIRST_PLAYED   = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#firstUsed");
0054         static const QString XESAM_LAST_PLAYED    = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#lastUsed");
0055 
0056         static const QString XESAM_ID             = QStringLiteral("http://freedesktop.org/standards/xesam/1.0/core#id");
0057         //static bool conversionMapsInitialised = false;
0058 
0059 QVariantMap
0060 Meta::Field::mapFromTrack( const Meta::TrackPtr &track )
0061 {
0062     QVariantMap map;
0063     if( !track )
0064         return map;
0065 
0066     if( track->name().isEmpty() )
0067         map.insert( Meta::Field::TITLE, QVariant( track->prettyName() ) );
0068     else
0069         map.insert( Meta::Field::TITLE, QVariant( track->name() ) );
0070     if( track->artist() && !track->artist()->name().isEmpty() )
0071         map.insert( Meta::Field::ARTIST, QVariant( track->artist()->name() ) );
0072     if( track->album() && !track->album()->name().isEmpty() )
0073     {
0074         map.insert( Meta::Field::ALBUM, QVariant( track->album()->name() ) );
0075         if( track->album()->hasAlbumArtist() && !track->album()->albumArtist()->name().isEmpty() )
0076             map.insert( Meta::Field::ALBUMARTIST, QVariant( track->album()->albumArtist()->name() ) );
0077     }
0078     if( track->filesize() )
0079         map.insert( Meta::Field::FILESIZE, QVariant( track->filesize() ) );
0080     if( track->genre() && !track->genre()->name().isEmpty() )
0081         map.insert( Meta::Field::GENRE, QVariant( track->genre()->name() ) );
0082     if( track->composer() && !track->composer()->name().isEmpty() )
0083         map.insert( Meta::Field::COMPOSER, QVariant( track->composer()->name() ) );
0084     if( track->year() && !track->year()->name().isEmpty() )
0085         map.insert( Meta::Field::YEAR, QVariant( track->year()->name() ) );
0086     if( !track->comment().isEmpty() )
0087         map.insert( Meta::Field::COMMENT, QVariant( track->comment() ) );
0088     if( track->trackNumber() )
0089         map.insert( Meta::Field::TRACKNUMBER, QVariant( track->trackNumber() ) );
0090     if( track->discNumber() )
0091         map.insert( Meta::Field::DISCNUMBER, QVariant( track->discNumber() ) );
0092     if( track->bitrate() )
0093         map.insert( Meta::Field::BITRATE, QVariant( track->bitrate() ) );
0094     if( track->length() )
0095         map.insert( Meta::Field::LENGTH, QVariant( track->length() ) );
0096     if( track->sampleRate() )
0097         map.insert( Meta::Field::SAMPLERATE, QVariant( track->sampleRate() ) );
0098     if( track->bpm() >= 0.0)
0099         map.insert( Meta::Field::BPM, QVariant( track->bpm() ) );
0100 
0101     map.insert( Meta::Field::UNIQUEID, QVariant( track->uidUrl() ) );
0102     map.insert( Meta::Field::URL, QVariant( track->prettyUrl() ) );
0103     Meta::ConstStatisticsPtr statistics = track->statistics();
0104     map.insert( Meta::Field::RATING, QVariant( statistics->rating() ) );
0105     map.insert( Meta::Field::SCORE, QVariant( statistics->score() ) );
0106     map.insert( Meta::Field::PLAYCOUNT, QVariant( statistics->playCount() ) );
0107     map.insert( Meta::Field::LAST_PLAYED, QVariant( statistics->lastPlayed() ) );
0108     map.insert( Meta::Field::FIRST_PLAYED, QVariant( statistics->firstPlayed() ) );
0109 
0110     return map;
0111 }
0112 
0113 QVariantMap
0114 Meta::Field::mprisMapFromTrack( const Meta::TrackPtr &track )
0115 {
0116     DEBUG_BLOCK
0117 
0118     QVariantMap map;
0119     if( track )
0120     {
0121         // MANDATORY:
0122         map[QStringLiteral("location")] = track->playableUrl().url();
0123         // INFORMATIONAL:
0124         map[QStringLiteral("title")] = track->prettyName();
0125 
0126         if( track->artist() )
0127             map[QStringLiteral("artist")] = track->artist()->name();
0128 
0129         if( track->album() )
0130         {
0131             map[QStringLiteral("album")] = track->album()->name();
0132             if( track->album()->hasAlbumArtist() && !track->album()->albumArtist()->name().isEmpty() )
0133                 map[ QStringLiteral("albumartist") ] = track->album()->albumArtist()->name();
0134 
0135             QUrl url = track->album()->imageLocation();
0136             if ( url.isValid() && !url.isLocalFile() )
0137             {
0138                 // embedded id?  Request a version to be put in the cache
0139                 QImage image = track->album()->image();
0140                 int width = track->album()->image().width();
0141                 url = track->album()->imageLocation( width );
0142                 debug() << "MPRIS: New location for width" << width << "is" << url;
0143             }
0144             if ( url.isValid() && url.isLocalFile() )
0145                 map[QStringLiteral("arturl")] = QString::fromLatin1( url.toEncoded() );
0146         }
0147 
0148         map[QStringLiteral("tracknumber")] = track->trackNumber();
0149         map[QStringLiteral("time")] = track->length() / 1000;
0150         map[QStringLiteral("mtime")] = track->length();
0151 
0152         if( track->genre() )
0153             map[QStringLiteral("genre")] = track->genre()->name();
0154 
0155         map[QStringLiteral("comment")] = track->comment();
0156         map[QStringLiteral("rating")] = track->statistics()->rating()/2;  //out of 5, not 10.
0157 
0158         if( track->year() )
0159             map[QStringLiteral("year")] = track->year()->name();
0160 
0161         //TODO: external service meta info
0162 
0163         // TECHNICAL:
0164         map[QStringLiteral("audio-bitrate")] = track->bitrate();
0165         map[QStringLiteral("audio-samplerate")] = track->sampleRate();
0166         //amarok has no video-bitrate
0167 
0168         // EXTRA Amarok specific
0169         const QString lyrics = track->cachedLyrics();
0170         if( !lyrics.isEmpty() )
0171             map[QStringLiteral("lyrics")] = lyrics;
0172     }
0173     return map;
0174 }
0175 
0176 QVariantMap
0177 Meta::Field::mpris20MapFromTrack( const Meta::TrackPtr &track )
0178 {
0179     DEBUG_BLOCK
0180 
0181     QVariantMap map;
0182     if( track )
0183     {
0184         // We do not set mpris::trackid here because it depends on the position
0185         // of the track in the playlist
0186         map[QStringLiteral("mpris:length")] = track->length() * 1000; // microseconds
0187 
0188         // get strong pointers (BR 317980)
0189         Meta::AlbumPtr    album = track->album();
0190         Meta::ArtistPtr   artist = track->artist();
0191         Meta::ComposerPtr composer = track->composer();
0192         Meta::YearPtr     year = track->year();
0193         Meta::GenrePtr    genre = track->genre();
0194         Meta::ConstStatisticsPtr statistics = track->statistics();
0195 
0196         if( album ) {
0197             QUrl url = album->imageLocation();
0198             debug() << "MPRIS2: Album image location is" << url;
0199             if ( url.isValid() && !url.isLocalFile() )
0200             {
0201                 // embedded id?  Request a version to be put in the cache
0202                 const QImage image = album->image();
0203                 int width = image.width();
0204                 url = album->imageLocation( width );
0205                 debug() << "MPRIS2: New location for width" << width << "is" << url;
0206             }
0207             if ( url.isValid() && url.isLocalFile() )
0208                 map[QStringLiteral("mpris:artUrl")] = QString::fromLatin1( url.toEncoded() );
0209 
0210             map[QStringLiteral("xesam:album")] = album->name();
0211             if ( album->hasAlbumArtist() )
0212                 map[QStringLiteral("xesam:albumArtist")] = QStringList() << album->albumArtist()->name();
0213         }
0214 
0215         if( artist )
0216             map[QStringLiteral("xesam:artist")] = QStringList() << artist->name();
0217 
0218         const QString lyrics = track->cachedLyrics();
0219         if( !lyrics.isEmpty() )
0220             map[QStringLiteral("xesam:asText")] = lyrics;
0221 
0222         if( track->bpm() > 0 )
0223             map[QStringLiteral("xesam:audioBPM")] = int(track->bpm());
0224 
0225         map[QStringLiteral("xesam:autoRating")] = statistics->score();
0226 
0227         map[QStringLiteral("xesam:comment")] = QStringList() << track->comment();
0228 
0229         if( composer )
0230             map[QStringLiteral("xesam:composer")] = QStringList() << composer->name();
0231 
0232         if( year )
0233             map[QStringLiteral("xesam:contentCreated")] = QDate(year->year(), 1, 1).startOfDay().toString(Qt::ISODate);
0234 
0235         if( track->discNumber() )
0236             map[QStringLiteral("xesam:discNumber")] = track->discNumber();
0237 
0238         if( statistics->firstPlayed().isValid() )
0239             map[QStringLiteral("xesam:firstUsed")] = statistics->firstPlayed().toString(Qt::ISODate);
0240 
0241         if( genre )
0242             map[QStringLiteral("xesam:genre")] = QStringList() << genre->name();
0243 
0244         if( statistics->lastPlayed().isValid() )
0245             map[QStringLiteral("xesam:lastUsed")] = statistics->lastPlayed().toString(Qt::ISODate);
0246 
0247         map[QStringLiteral("xesam:title")] = track->prettyName();
0248 
0249         map[QStringLiteral("xesam:trackNumber")] = track->trackNumber();
0250 
0251         map[QStringLiteral("xesam:url")] = track->playableUrl().url();
0252 
0253         map[QStringLiteral("xesam:useCount")] = statistics->playCount();
0254 
0255         map[QStringLiteral("xesam:userRating")] = statistics->rating() / 10.; // xesam:userRating is a float
0256     }
0257     return map;
0258 }
0259 
0260 
0261 void
0262 Meta::Field::updateTrack( Meta::TrackPtr track, const QVariantMap &metadata )
0263 {
0264     if( !track )
0265         return;
0266     Meta::TrackEditorPtr ec = track->editor();
0267     if( !ec )
0268         return;
0269 
0270     ec->beginUpdate();
0271     QString title = metadata.contains( Meta::Field::TITLE ) ?
0272                             metadata.value( Meta::Field::TITLE ).toString() : QString();
0273     ec->setTitle( title );
0274     QString comment = metadata.contains( Meta::Field::COMMENT ) ?
0275                             metadata.value( Meta::Field::COMMENT ).toString() : QString();
0276     ec->setComment( comment );
0277     int tracknr = metadata.contains( Meta::Field::TRACKNUMBER ) ?
0278                             metadata.value( Meta::Field::TRACKNUMBER ).toInt() : 0;
0279     ec->setTrackNumber( tracknr );
0280     int discnr = metadata.contains( Meta::Field::DISCNUMBER ) ?
0281                             metadata.value( Meta::Field::DISCNUMBER ).toInt() : 0;
0282     ec->setDiscNumber( discnr );
0283     QString artist = metadata.contains( Meta::Field::ARTIST ) ?
0284                             metadata.value( Meta::Field::ARTIST ).toString() : QString();
0285     ec->setArtist( artist );
0286     QString album = metadata.contains( Meta::Field::ALBUM ) ?
0287                             metadata.value( Meta::Field::ALBUM ).toString() : QString();
0288     ec->setAlbum( album );
0289     QString albumArtist = metadata.contains( Meta::Field::ALBUMARTIST ) ?
0290                             metadata.value( Meta::Field::ALBUMARTIST ).toString() : QString();
0291     ec->setAlbumArtist(albumArtist);
0292     QString genre = metadata.contains( Meta::Field::GENRE ) ?
0293                             metadata.value( Meta::Field::GENRE ).toString() : QString();
0294     ec->setGenre( genre );
0295     QString composer = metadata.contains( Meta::Field::COMPOSER ) ?
0296                             metadata.value( Meta::Field::COMPOSER ).toString() : QString();
0297     ec->setComposer( composer );
0298     int year = metadata.contains( Meta::Field::YEAR ) ?
0299                             metadata.value( Meta::Field::YEAR ).toInt() : 0;
0300     ec->setYear( year );
0301     ec->endUpdate();
0302 }
0303 
0304 QString
0305 Meta::Field::xesamPrettyToFullFieldName( const QString &name )
0306 {
0307     if( name == Meta::Field::ARTIST )
0308         return XESAM_ARTIST;
0309     else if( name == Meta::Field::ALBUM )
0310         return XESAM_ALBUM;
0311     else if( name == Meta::Field::ALBUMARTIST )
0312         return XESAM_ALBUMARTIST;
0313     else if( name == Meta::Field::BITRATE )
0314         return XESAM_BITRATE;
0315     else if( name == Meta::Field::BPM )
0316         return XESAM_BPM;
0317     else if( name == Meta::Field::CODEC )
0318         return XESAM_CODEC;
0319     else if( name == Meta::Field::COMMENT )
0320         return XESAM_COMMENT;
0321     else if( name == Meta::Field::COMPOSER )
0322         return XESAM_COMPOSER;
0323     else if( name == Meta::Field::DISCNUMBER )
0324         return XESAM_DISCNUMBER;
0325     else if( name == Meta::Field::FILESIZE )
0326         return XESAM_FILESIZE;
0327     else if( name == Meta::Field::GENRE )
0328         return XESAM_GENRE;
0329     else if( name == Meta::Field::LENGTH )
0330         return XESAM_LENGTH;
0331     else if( name == Meta::Field::RATING )
0332         return XESAM_RATING;
0333     else if( name == Meta::Field::SAMPLERATE )
0334         return XESAM_SAMPLERATE;
0335     else if( name == Meta::Field::TITLE )
0336         return XESAM_TITLE;
0337     else if( name == Meta::Field::TRACKNUMBER )
0338         return XESAM_TRACKNUMBER;
0339     else if( name == Meta::Field::URL )
0340         return XESAM_URL;
0341     else if( name == Meta::Field::YEAR )
0342         return XESAM_YEAR;
0343     else if( name==Meta::Field::SCORE )
0344         return XESAM_SCORE;
0345     else if( name==Meta::Field::PLAYCOUNT )
0346         return XESAM_PLAYCOUNT;
0347     else if( name==Meta::Field::FIRST_PLAYED )
0348         return XESAM_FIRST_PLAYED;
0349     else if( name==Meta::Field::LAST_PLAYED )
0350         return XESAM_LAST_PLAYED;
0351     else if( name==Meta::Field::UNIQUEID )
0352         return XESAM_ID;
0353     else
0354         return "xesamPrettyToFullName: unknown name " + name;
0355 }
0356 
0357 QString
0358 Meta::Field::xesamFullToPrettyFieldName( const QString &name )
0359 {
0360     if( name == XESAM_ARTIST )
0361         return Meta::Field::ARTIST;
0362     if( name == XESAM_ALBUMARTIST )
0363         return Meta::Field::ALBUMARTIST;
0364     else if( name == XESAM_ALBUM )
0365         return Meta::Field::ALBUM;
0366     else if( name == XESAM_BITRATE )
0367         return Meta::Field::BITRATE;
0368     else if( name == XESAM_BPM )
0369         return Meta::Field::BPM;
0370     else if( name == XESAM_CODEC )
0371         return Meta::Field::CODEC;
0372     else if( name == XESAM_COMMENT )
0373         return Meta::Field::COMMENT;
0374     else if( name == XESAM_COMPOSER )
0375         return Meta::Field::COMPOSER;
0376     else if( name == XESAM_DISCNUMBER )
0377         return Meta::Field::DISCNUMBER;
0378     else if( name == XESAM_FILESIZE )
0379         return Meta::Field::FILESIZE;
0380     else if( name == XESAM_GENRE )
0381         return Meta::Field::GENRE;
0382     else if( name == XESAM_LENGTH )
0383         return Meta::Field::LENGTH;
0384     else if( name == XESAM_RATING )
0385         return Meta::Field::RATING;
0386     else if( name == XESAM_SAMPLERATE )
0387         return Meta::Field::SAMPLERATE;
0388     else if( name == XESAM_TITLE )
0389         return Meta::Field::TITLE;
0390     else if( name == XESAM_TRACKNUMBER )
0391         return Meta::Field::TRACKNUMBER;
0392     else if( name == XESAM_URL )
0393         return Meta::Field::URL;
0394     else if( name == XESAM_YEAR )
0395         return Meta::Field::YEAR;
0396     else if( name == XESAM_SCORE )
0397         return Meta::Field::SCORE;
0398     else if( name == XESAM_PLAYCOUNT )
0399         return Meta::Field::PLAYCOUNT;
0400     else if( name == XESAM_FIRST_PLAYED )
0401         return Meta::Field::FIRST_PLAYED;
0402     else if( name == XESAM_LAST_PLAYED )
0403         return Meta::Field::LAST_PLAYED;
0404     else if( name == XESAM_ID )
0405         return Meta::Field::UNIQUEID;
0406     else
0407         return "xesamFullToPrettyName: unknown name " + name;
0408 }
0409 
0410 
0411 QString
0412 Meta::msToPrettyTime( qint64 ms )
0413 {
0414     return Meta::secToPrettyTime( ms / 1000 );
0415 }
0416 
0417 QString
0418 Meta::secToPrettyTime( int seconds )
0419 {
0420     if( seconds < 60 * 60 ) // one hour
0421         return QTime(0, 0, 0).addSecs( seconds ).toString( i18nc("the time format for a time length when the time is below 1 hour see QTime documentation.", "m:ss" ) );
0422     // split days off for manual formatting (QTime doesn't work properly > 1 day,
0423     // QDateTime isn't suitable as it thinks it's a date)
0424     int days = seconds / 86400;
0425     seconds %= 86400;
0426     QString reply = QLatin1String("");
0427     if ( days > 0 )
0428         reply += i18ncp("number of days with spacing for the pretty time", "%1 day, ", "%1 days, ", days);
0429     reply += QTime(0, 0, 0).addSecs( seconds ).toString( i18nc("the time format for a time length when the time is 1 hour or above see QTime documentation.", "h:mm:ss" ) );
0430     return reply;
0431 }
0432 
0433 QString
0434 Meta::secToPrettyTimeLong( int seconds )
0435 {
0436     int minutes = seconds / 60;
0437     int hours = minutes / 60;
0438     int days = hours / 24;
0439     int months = days / 30; // a short month
0440     int years = months / 12;
0441 
0442     if( months > 24 || (((months % 12) == 0) && years > 0) )
0443         return i18ncp("number of years for the pretty time", "%1 year", "%1 years", years);
0444     if( days > 60 || (((days % 30) == 0) && months > 0) )
0445         return i18ncp("number of months for the pretty time", "%1 month", "%1 months", months);
0446     if( hours > 24  || (((hours % 24) == 0) && days > 0) )
0447         return i18ncp("number of days for the pretty time", "%1 day", "%1 days", days);
0448     if( minutes > 120 || (((minutes % 60) == 0) && hours > 0) )
0449         return i18ncp("number of hours for the pretty time", "%1 hour", "%1 hours", hours);
0450     if( seconds > 120 || (((seconds % 60) == 0) && minutes > 0) )
0451         return i18ncp("number of minutes for the pretty time", "%1 minute", "%1 minutes", hours);
0452 
0453     return i18ncp("number of seconds for the pretty time", "%1 second", "%1 seconds", hours);
0454 }
0455 
0456 QString
0457 Meta::prettyFilesize( quint64 size )
0458 {
0459     return KIO::convertSize( size );
0460 }
0461 
0462 QString
0463 Meta::prettyBitrate( int bitrate )
0464 {
0465     //the point here is to force sharing of these strings returned from prettyBitrate()
0466     static const QString bitrateStore[9] = {
0467         "?", "32", "64", "96", "128", "160", "192", "224", "256" };
0468 
0469     return (bitrate >=0 && bitrate <= 256 && bitrate % 32 == 0)
0470                 ? bitrateStore[ bitrate / 32 ]
0471     : QStringLiteral( "%1" ).arg( bitrate );
0472 }