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 }