void TagReader::Decode(const QString& tag, const QTextCodec* codec, std::string* output) { if (!codec) { output->assign(DataCommaSizeFromQString(tag)); } else { const QString decoded(codec->toUnicode(tag.toUtf8())); output->assign(DataCommaSizeFromQString(decoded)); } }
void SpotifyServer::Login(const QString& username, const QString& password, pb::spotify::Bitrate bitrate, bool volume_normalisation) { pb::spotify::Message message; pb::spotify::LoginRequest* request = message.mutable_login_request(); request->set_username(DataCommaSizeFromQString(username)); if (!password.isEmpty()) { request->set_password(DataCommaSizeFromQString(password)); } request->mutable_playback_settings()->set_bitrate(bitrate); request->mutable_playback_settings()->set_volume_normalisation( volume_normalisation); SendOrQueueMessage(message); }
void SpotifyServer::AlbumBrowse(const QString& uri) { pb::spotify::Message message; pb::spotify::BrowseAlbumRequest* req = message.mutable_browse_album_request(); req->set_uri(DataCommaSizeFromQString(uri)); SendOrQueueMessage(message); }
void SpotifyServer::LoadImage(const QString& id) { pb::spotify::Message message; pb::spotify::ImageRequest* req = message.mutable_image_request(); req->set_id(DataCommaSizeFromQString(id)); SendOrQueueMessage(message); }
TagReaderReply* TagReaderClient::ReadCloudFile( const QUrl& download_url, const QString& title, int size, const QString& mime_type, const QString& authorisation_header) { pb::tagreader::Message message; pb::tagreader::ReadCloudFileRequest* req = message.mutable_read_cloud_file_request(); const QString url_string = download_url.toEncoded(); req->set_download_url(DataCommaSizeFromQString(url_string)); req->set_title(DataCommaSizeFromQString(title)); req->set_size(size); req->set_mime_type(DataCommaSizeFromQString(mime_type)); req->set_authorisation_header(DataCommaSizeFromQString(authorisation_header)); return worker_pool_->SendMessageWithReply(&message); }
void SpotifyServer::StartPlayback(const QString& uri, quint16 port) { pb::spotify::Message message; pb::spotify::PlaybackRequest* req = message.mutable_playback_request(); req->set_track_uri(DataCommaSizeFromQString(uri)); req->set_media_port(port); SendOrQueueMessage(message); }
TagReaderReply* TagReaderClient::ReadFile(const QString& filename) { pb::tagreader::Message message; pb::tagreader::ReadFileRequest* req = message.mutable_read_file_request(); req->set_filename(DataCommaSizeFromQString(filename)); return worker_pool_->SendMessageWithReply(&message); }
void SpotifyServer::Search(const QString& text, int limit, int limit_album) { pb::spotify::Message message; pb::spotify::SearchRequest* req = message.mutable_search_request(); req->set_query(DataCommaSizeFromQString(text)); req->set_limit(limit); req->set_limit_album(limit_album); SendOrQueueMessage(message); }
TagReaderReply* TagReaderClient::SaveFile(const QString& filename, const Song& metadata) { pb::tagreader::Message message; pb::tagreader::SaveFileRequest* req = message.mutable_save_file_request(); req->set_filename(DataCommaSizeFromQString(filename)); metadata.ToProtobuf(req->mutable_metadata()); return worker_pool_->SendMessageWithReply(&message); }
TagReaderReply* TagReaderClient::UpdateSongRating(const Song& metadata) { pb::tagreader::Message message; pb::tagreader::SaveSongRatingToFileRequest* req = message.mutable_save_song_rating_to_file_request(); req->set_filename(DataCommaSizeFromQString(metadata.url().toLocalFile())); metadata.ToProtobuf(req->mutable_metadata()); return worker_pool_->SendMessageWithReply(&message); }
void SpotifyServer::AddSongsToPlaylist(int playlist_index, const QList<QUrl>& songs_urls) { pb::spotify::Message message; pb::spotify::AddTracksToPlaylistRequest* req = message.mutable_add_tracks_to_playlist(); req->set_playlist_index(playlist_index); for (const QUrl& song_url : songs_urls) { req->add_track_uri(DataCommaSizeFromQString(song_url.toString())); } SendOrQueueMessage(message); }
void TagReader::Decode(const TagLib::String& tag, const QTextCodec* codec, std::string* output) { QString tmp; if (codec && tag.isLatin1()) { // Never override UTF-8. const std::string fixed = QString::fromUtf8(tag.toCString(true)).toStdString(); tmp = codec->toUnicode(fixed.c_str()).trimmed(); } else { tmp = TStringToQString(tag).trimmed(); } output->assign(DataCommaSizeFromQString(tmp)); }
void TagReader::ReadFile(const QString& filename, pb::tagreader::SongMetadata* song) const { const QByteArray url(QUrl::fromLocalFile(filename).toEncoded()); const QFileInfo info(filename); qLog(Debug) << "Reading tags from" << filename; song->set_basefilename(DataCommaSizeFromQString(info.fileName())); song->set_url(url.constData(), url.size()); song->set_filesize(info.size()); song->set_mtime(info.lastModified().toTime_t()); song->set_ctime(info.created().toTime_t()); std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename)); if (fileref->isNull()) { qLog(Info) << "TagLib hasn't been able to read " << filename << " file"; return; } TagLib::Tag* tag = fileref->tag(); if (tag) { Decode(tag->title(), nullptr, song->mutable_title()); Decode(tag->artist(), nullptr, song->mutable_artist()); // TPE1 Decode(tag->album(), nullptr, song->mutable_album()); Decode(tag->genre(), nullptr, song->mutable_genre()); song->set_year(tag->year()); song->set_track(tag->track()); song->set_valid(true); } QString disc; QString compilation; // Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same // way; // apart, so we keep specific behavior for some formats by adding another // "else if" block below. if (TagLib::Ogg::XiphComment* tag = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) { ParseOggTag(tag->fieldListMap(), nullptr, &disc, &compilation, song); } if (TagLib::MPEG::File* file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) { if (file->ID3v2Tag()) { const TagLib::ID3v2::FrameListMap& map = file->ID3v2Tag()->frameListMap(); if (!map["TPOS"].isEmpty()) disc = TStringToQString(map["TPOS"].front()->toString()).trimmed(); if (!map["TBPM"].isEmpty()) song->set_bpm(TStringToQString(map["TBPM"].front()->toString()) .trimmed() .toFloat()); if (!map["TCOM"].isEmpty()) Decode(map["TCOM"].front()->toString(), nullptr, song->mutable_composer()); if (!map["TIT1"].isEmpty()) // content group Decode(map["TIT1"].front()->toString(), nullptr, song->mutable_grouping()); // Skip TPE1 (which is the artist) here because we already fetched it if (!map["TPE2"].isEmpty()) // non-standard: Apple, Microsoft Decode(map["TPE2"].front()->toString(), nullptr, song->mutable_albumartist()); if (!map["TCMP"].isEmpty()) compilation = TStringToQString(map["TCMP"].front()->toString()).trimmed(); if (!map["APIC"].isEmpty()) song->set_art_automatic(kEmbeddedCover); // Find a suitable comment tag. For now we ignore iTunNORM comments. for (int i = 0; i < map["COMM"].size(); ++i) { const TagLib::ID3v2::CommentsFrame* frame = dynamic_cast<const TagLib::ID3v2::CommentsFrame*>(map["COMM"][i]); if (frame && TStringToQString(frame->description()) != "iTunNORM") { Decode(frame->text(), nullptr, song->mutable_comment()); break; } } // Parse FMPS frames for (int i = 0; i < map["TXXX"].size(); ++i) { const TagLib::ID3v2::UserTextIdentificationFrame* frame = dynamic_cast<const TagLib::ID3v2::UserTextIdentificationFrame*>( map["TXXX"][i]); if (frame && frame->description().startsWith("FMPS_")) { ParseFMPSFrame(TStringToQString(frame->description()), TStringToQString(frame->fieldList()[1]), song); } } // Check POPM tags // We do this after checking FMPS frames, so FMPS have precedence, as we // will consider POPM tags iff song has no rating/playcount already set. if (!map["POPM"].isEmpty()) { const TagLib::ID3v2::PopularimeterFrame* frame = dynamic_cast<const TagLib::ID3v2::PopularimeterFrame*>( map["POPM"].front()); if (frame) { // Take a user rating only if there's no rating already set if (song->rating() <= 0 && frame->rating() > 0) { song->set_rating(ConvertPOPMRating(frame->rating())); } if (song->playcount() <= 0 && frame->counter() > 0) { song->set_playcount(frame->counter()); } } } } } else if (TagLib::FLAC::File* file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) { if (file->xiphComment()) { ParseOggTag(file->xiphComment()->fieldListMap(), nullptr, &disc, &compilation, song); #ifdef TAGLIB_HAS_FLAC_PICTURELIST if (!file->pictureList().isEmpty()) { song->set_art_automatic(kEmbeddedCover); } #endif } Decode(tag->comment(), nullptr, song->mutable_comment()); } else if (TagLib::MP4::File* file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) { if (file->tag()) { TagLib::MP4::Tag* mp4_tag = file->tag(); const TagLib::MP4::ItemListMap& items = mp4_tag->itemListMap(); // Find album artists TagLib::MP4::ItemListMap::ConstIterator it = items.find("aART"); if (it != items.end()) { TagLib::StringList album_artists = it->second.toStringList(); if (!album_artists.isEmpty()) { Decode(album_artists.front(), nullptr, song->mutable_albumartist()); } } // Find album cover art if (items.find("covr") != items.end()) { song->set_art_automatic(kEmbeddedCover); } if (items.contains("disk")) { disc = TStringToQString( TagLib::String::number(items["disk"].toIntPair().first)); } if (items.contains(kMP4_FMPS_Rating_ID)) { float rating = TStringToQString(items[kMP4_FMPS_Rating_ID].toStringList().toString( '\n')).toFloat(); if (song->rating() <= 0 && rating > 0) { song->set_rating(rating); } } if (items.contains(kMP4_FMPS_Playcount_ID)) { int playcount = TStringToQString( items[kMP4_FMPS_Playcount_ID].toStringList().toString('\n')) .toFloat(); if (song->playcount() <= 0 && playcount > 0) { song->set_playcount(playcount); } } if (items.contains(kMP4_FMPS_Playcount_ID)) { int score = TStringToQString( items[kMP4_FMPS_Score_ID].toStringList().toString('\n')) .toFloat() * 100; if (song->score() <= 0 && score > 0) { song->set_score(score); } } if (items.contains("\251wrt")) { Decode(items["\251wrt"].toStringList().toString(", "), nullptr, song->mutable_composer()); } if (items.contains("\251grp")) { Decode(items["\251grp"].toStringList().toString(" "), nullptr, song->mutable_grouping()); } Decode(mp4_tag->comment(), nullptr, song->mutable_comment()); } } #ifdef TAGLIB_WITH_ASF else if (TagLib::ASF::File* file = dynamic_cast<TagLib::ASF::File*>(fileref->file())) { const TagLib::ASF::AttributeListMap& attributes_map = file->tag()->attributeListMap(); if (attributes_map.contains("FMPS/Rating")) { const TagLib::ASF::AttributeList& attributes = attributes_map["FMPS/Rating"]; if (!attributes.isEmpty()) { float rating = TStringToQString(attributes.front().toString()).toFloat(); if (song->rating() <= 0 && rating > 0) { song->set_rating(rating); } } } if (attributes_map.contains("FMPS/Playcount")) { const TagLib::ASF::AttributeList& attributes = attributes_map["FMPS/Playcount"]; if (!attributes.isEmpty()) { int playcount = TStringToQString(attributes.front().toString()).toInt(); if (song->playcount() <= 0 && playcount > 0) { song->set_playcount(playcount); } } } if (attributes_map.contains("FMPS/Rating_Amarok_Score")) { const TagLib::ASF::AttributeList& attributes = attributes_map["FMPS/Rating_Amarok_Score"]; if (!attributes.isEmpty()) { int score = TStringToQString(attributes.front().toString()).toFloat() * 100; if (song->score() <= 0 && score > 0) { song->set_score(score); } } } } #endif else if (tag) { Decode(tag->comment(), nullptr, song->mutable_comment()); } if (!disc.isEmpty()) { const int i = disc.indexOf('/'); if (i != -1) { // disc.right( i ).toInt() is total number of discs, we don't use this at // the moment song->set_disc(disc.left(i).toInt()); } else { song->set_disc(disc.toInt()); } } if (compilation.isEmpty()) { // well, it wasn't set, but if the artist is VA assume it's a compilation if (QStringFromStdString(song->artist()).toLower() == "various artists") { song->set_compilation(true); } } else { song->set_compilation(compilation.toInt() == 1); } if (fileref->audioProperties()) { song->set_bitrate(fileref->audioProperties()->bitrate()); song->set_samplerate(fileref->audioProperties()->sampleRate()); song->set_length_nanosec(fileref->audioProperties()->length() * kNsecPerSec); } // Get the filetype if we can song->set_type(GuessFileType(fileref.get())); // Set integer fields to -1 if they're not valid #define SetDefault(field) \ if (song->field() <= 0) { \ song->set_##field(-1); \ } SetDefault(track); SetDefault(disc); SetDefault(bpm); SetDefault(year); SetDefault(bitrate); SetDefault(samplerate); SetDefault(lastplayed); #undef SetDefault }
void SongSender::SendSingleSong(DownloadItem download_item) { // Only local files!!! if (!(download_item.song_.url().scheme() == "file")) return; QString local_file = download_item.song_.url().toLocalFile(); bool is_transcoded = transcoder_map_.contains(local_file); if (is_transcoded) { local_file = transcoder_map_.take(local_file); } // Open the file QFile file(local_file); // Get sha1 for file QByteArray sha1 = Utilities::Sha1File(file).toHex(); qLog(Debug) << "sha1 for file" << local_file << "=" << sha1; file.open(QIODevice::ReadOnly); QByteArray data; pb::remote::Message msg; pb::remote::ResponseSongFileChunk* chunk = msg.mutable_response_song_file_chunk(); msg.set_type(pb::remote::SONG_FILE_CHUNK); QImage null_image; // Calculate the number of chunks int chunk_count = qRound((file.size() / kFileChunkSize) + 0.5); int chunk_number = 1; while (!file.atEnd()) { // Read file chunk data = file.read(kFileChunkSize); // Set chunk data chunk->set_chunk_count(chunk_count); chunk->set_chunk_number(chunk_number); chunk->set_file_count(download_item.song_count_); chunk->set_file_number(download_item.song_no_); chunk->set_size(file.size()); chunk->set_data(data.data(), data.size()); chunk->set_file_hash(sha1.data(), sha1.size()); // On the first chunk send the metadata, so the client knows // what file it receives. if (chunk_number == 1) { int i = app_->playlist_manager()->active()->current_row(); pb::remote::SongMetadata* song_metadata = msg.mutable_response_song_file_chunk()->mutable_song_metadata(); OutgoingDataCreator::CreateSong(download_item.song_, null_image, i,song_metadata); // if the file was transcoded, we have to change the filename and filesize if (is_transcoded) { song_metadata->set_file_size(file.size()); QString basefilename = download_item.song_.basefilename(); QFileInfo info(basefilename); basefilename.replace("." + info.suffix(), "." + transcoder_preset_.extension_); song_metadata->set_filename(DataCommaSizeFromQString(basefilename)); } } // Send data directly to the client client_->SendData(&msg); // Clear working data chunk->Clear(); data.clear(); chunk_number++; } // If the file was transcoded, delete the temporary one if (is_transcoded) { file.remove(); } else { file.close(); } }