예제 #1
0
void SpotifyService::SongFromProtobuf(const pb::spotify::Track& track,
                                      Song* song) {
  song->set_rating(track.starred() ? 1.0 : 0.0);
  song->set_title(QStringFromStdString(track.title()));
  song->set_album(QStringFromStdString(track.album()));
  song->set_length_nanosec(track.duration_msec() * kNsecPerMsec);
  song->set_score(track.popularity());
  song->set_disc(track.disc());
  song->set_track(track.track());
  song->set_year(track.year());
  song->set_url(QUrl(QStringFromStdString(track.uri())));
  song->set_art_automatic("spotify://image/" +
                          QStringFromStdString(track.album_art_id()));

  QStringList artists;
  for (int i = 0; i < track.artist_size(); ++i) {
    artists << QStringFromStdString(track.artist(i));
  }

  song->set_artist(artists.join(", "));

  song->set_filetype(Song::Type_Stream);
  song->set_valid(true);
  song->set_directory_id(0);
  song->set_mtime(0);
  song->set_ctime(0);
  song->set_filesize(0);
}
예제 #2
0
void SpotifyServer::MessageArrived(const pb::spotify::Message& message) {
  if (message.has_login_response()) {
    const pb::spotify::LoginResponse& response = message.login_response();
    logged_in_ = response.success();

    if (response.success()) {
      // Send any messages that were queued before the client logged in
      for (const pb::spotify::Message& message : queued_messages_) {
        SendOrQueueMessage(message);
      }
      queued_messages_.clear();
    }

    emit LoginCompleted(response.success(),
                        QStringFromStdString(response.error()),
                        response.error_code());
  } else if (message.has_playlists_updated()) {
    emit PlaylistsUpdated(message.playlists_updated());
  } else if (message.has_load_playlist_response()) {
    const pb::spotify::LoadPlaylistResponse& response =
        message.load_playlist_response();

    switch (response.request().type()) {
      case pb::spotify::Inbox:
        emit InboxLoaded(response);
        break;

      case pb::spotify::Starred:
        emit StarredLoaded(response);
        break;

      case pb::spotify::UserPlaylist:
        emit UserPlaylistLoaded(response);
        break;
    }
  } else if (message.has_playback_error()) {
    emit PlaybackError(QStringFromStdString(message.playback_error().error()));
  } else if (message.has_search_response()) {
    emit SearchResults(message.search_response());
  } else if (message.has_image_response()) {
    const pb::spotify::ImageResponse& response = message.image_response();
    const QString id = QStringFromStdString(response.id());

    if (response.has_data()) {
      emit ImageLoaded(
          id, QImage::fromData(
                  QByteArray(response.data().data(), response.data().size())));
    } else {
      emit ImageLoaded(id, QImage());
    }
  } else if (message.has_sync_playlist_progress()) {
    emit SyncPlaylistProgress(message.sync_playlist_progress());
  } else if (message.has_browse_album_response()) {
    emit AlbumBrowseResults(message.browse_album_response());
  } else if (message.has_browse_toplist_response()) {
    emit ToplistBrowseResults(message.browse_toplist_response());
  }
}
예제 #3
0
void TagReaderWorker::MessageArrived(const pb::tagreader::Message& message) {
  pb::tagreader::Message reply;

#if 0
  // Crash every few requests
  if (qrand() % 10 == 0) {
    qLog(Debug) << "Crashing on request ID" << message.id();
    abort();
  }
#endif

  if (message.has_read_file_request()) {
    tag_reader_.ReadFile(
        QStringFromStdString(message.read_file_request().filename()),
        reply.mutable_read_file_response()->mutable_metadata());
  } else if (message.has_save_file_request()) {
    reply.mutable_save_file_response()->set_success(tag_reader_.SaveFile(
        QStringFromStdString(message.save_file_request().filename()),
        message.save_file_request().metadata()));
  } else if (message.has_save_song_statistics_to_file_request()) {
    reply.mutable_save_song_statistics_to_file_response()->set_success(
        tag_reader_.SaveSongStatisticsToFile(
            QStringFromStdString(
                message.save_song_statistics_to_file_request().filename()),
            message.save_song_statistics_to_file_request().metadata()));
  } else if (message.has_save_song_rating_to_file_request()) {
    reply.mutable_save_song_rating_to_file_response()->set_success(
        tag_reader_.SaveSongRatingToFile(
            QStringFromStdString(
                message.save_song_rating_to_file_request().filename()),
            message.save_song_rating_to_file_request().metadata()));
  } else if (message.has_is_media_file_request()) {
    reply.mutable_is_media_file_response()->set_success(tag_reader_.IsMediaFile(
        QStringFromStdString(message.is_media_file_request().filename())));
  } else if (message.has_load_embedded_art_request()) {
    QByteArray data = tag_reader_.LoadEmbeddedArt(
        QStringFromStdString(message.load_embedded_art_request().filename()));
    reply.mutable_load_embedded_art_response()->set_data(data.constData(),
                                                         data.size());
  } else if (message.has_read_cloud_file_request()) {
#ifdef HAVE_GOOGLE_DRIVE
    const pb::tagreader::ReadCloudFileRequest& req =
        message.read_cloud_file_request();
    if (!tag_reader_.ReadCloudFile(
             QUrl::fromEncoded(QByteArray(req.download_url().data(),
                                          req.download_url().size())),
             QStringFromStdString(req.title()), req.size(),
             QStringFromStdString(req.mime_type()),
             QStringFromStdString(req.authorisation_header()),
             reply.mutable_read_cloud_file_response()->mutable_metadata())) {
      reply.mutable_read_cloud_file_response()->clear_metadata();
    }
#endif
  }

  SendReply(message, &reply);
}
예제 #4
0
bool SpotifyService::DoPlaylistsDiffer(
    const pb::spotify::Playlists& response) const {
  if (playlists_.count() != response.playlist_size()) {
    return true;
  }

  for (int i = 0; i < response.playlist_size(); ++i) {
    const pb::spotify::Playlists::Playlist& msg = response.playlist(i);
    const QStandardItem* item = PlaylistBySpotifyIndex(msg.index());

    if (!item) {
      return true;
    }

    if (QStringFromStdString(msg.name()) != item->text()) {
      return true;
    }

    if (msg.nb_tracks() != item->rowCount()) {
      return true;
    }
  }

  return false;
}
예제 #5
0
void SpotifyService::SearchResults(
    const pb::spotify::SearchResponse& response) {
  if (QStringFromStdString(response.request().query()) != pending_search_) {
    qLog(Debug) << "Old search result for"
                << QStringFromStdString(response.request().query())
                << "expecting" << pending_search_;
    return;
  }
  pending_search_.clear();

  SongList songs;
  for (int i = 0; i < response.result_size(); ++i) {
    Song song;
    SongFromProtobuf(response.result(i), &song);
    songs << song;
  }

  qLog(Debug) << "Got" << songs.count() << "results";

  ClearSearchResults();

  // Fill results list
  for (const Song& song : songs) {
    QStandardItem* child = CreateSongItem(song);
    search_->appendRow(child);
  }

  const QString did_you_mean_suggestion =
      QStringFromStdString(response.did_you_mean());
  qLog(Debug) << "Did you mean suggestion: " << did_you_mean_suggestion;
  if (!did_you_mean_suggestion.isEmpty()) {
    search_box_->did_you_mean()->Show(did_you_mean_suggestion);
  } else {
    // In case something else was previously displayed
    search_box_->did_you_mean()->hide();
  }

  QModelIndex index = model()->merged_model()->mapFromSource(search_->index());
  ScrollToIndex(index);
}
예제 #6
0
void IncomingDataParser::InsertUrls(const pb::remote::Message& msg) {
  const pb::remote::RequestInsertUrls& request = msg.request_insert_urls();

  // Extract urls
  QList<QUrl> urls;
  for (auto it = request.urls().begin(); it != request.urls().end(); ++it) {
    std::string s = *it;
    urls << QUrl(QStringFromStdString(s));
  }

  // Insert the urls
  emit InsertUrls(request.playlist_id(), urls, request.position(),
                  request.play_now(), request.enqueue());
}
예제 #7
0
void SpotifySearchProvider::AlbumBrowseResponse(const pb::spotify::BrowseAlbumResponse& response) {
  QString uri = QStringFromStdString(response.uri());
  QMap<QString, int>::iterator it = pending_tracks_.find(uri);
  if (it == pending_tracks_.end())
    return;

  const int orig_id = it.value();
  pending_tracks_.erase(it);

  SongMimeData* mime_data = new SongMimeData;

  for (int i=0 ; i<response.track_size() ; ++i) {
    Song song;
    SpotifyService::SongFromProtobuf(response.track(i), &song);
    mime_data->songs << song;
  }

  emit TracksLoaded(orig_id, mime_data);
}
예제 #8
0
void SongSender::SendUrls(const pb::remote::RequestDownloadSongs &request) {
  SongList song_list;

  // First gather all valid songs
  for (auto it = request.urls().begin(); it != request.urls().end(); ++it) {
    std::string s = *it;
    QUrl url = QUrl(QStringFromStdString(s));

    Song song = app_->library_backend()->GetSongByUrl(url);

    if (song.is_valid() && song.url().scheme() == "file") {
      song_list.append(song);
    }
  }

  // Then send them to Clementine Remote
  for (Song s : song_list) {
    DownloadItem item(s, song_list.indexOf(s) + 1, song_list.count());
    download_queue_.append(item);
  }
}
예제 #9
0
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
}
예제 #10
0
void SpotifyService::PlaylistsUpdated(const pb::spotify::Playlists& response) {
  if (login_task_id_) {
    app_->task_manager()->SetTaskFinished(login_task_id_);
    login_task_id_ = 0;
  }

  // Create starred and inbox playlists if they're not here already
  if (!search_) {
    search_ =
        new QStandardItem(IconLoader::Load("edit-find", IconLoader::Base), 
                          tr("Search results"));
    search_->setToolTip(
        tr("Start typing something on the search box above to "
           "fill this search results list"));
    search_->setData(Type_SearchResults, InternetModel::Role_Type);
    search_->setData(InternetModel::PlayBehaviour_MultipleItems,
                     InternetModel::Role_PlayBehaviour);

    starred_ = new QStandardItem(QIcon(":/star-on.png"), tr("Starred"));
    starred_->setData(Type_StarredPlaylist, InternetModel::Role_Type);
    starred_->setData(true, InternetModel::Role_CanLazyLoad);
    starred_->setData(InternetModel::PlayBehaviour_MultipleItems,
                      InternetModel::Role_PlayBehaviour);
    starred_->setData(true, InternetModel::Role_CanBeModified);

    inbox_ = new QStandardItem(IconLoader::Load("mail-message", 
                               IconLoader::Base), tr("Inbox"));
    inbox_->setData(Type_InboxPlaylist, InternetModel::Role_Type);
    inbox_->setData(true, InternetModel::Role_CanLazyLoad);
    inbox_->setData(InternetModel::PlayBehaviour_MultipleItems,
                    InternetModel::Role_PlayBehaviour);

    toplist_ = new QStandardItem(QIcon(), tr("Top tracks"));
    toplist_->setData(Type_Toplist, InternetModel::Role_Type);
    toplist_->setData(true, InternetModel::Role_CanLazyLoad);
    toplist_->setData(InternetModel::PlayBehaviour_MultipleItems,
                      InternetModel::Role_PlayBehaviour);

    root_->appendRow(search_);
    root_->appendRow(toplist_);
    root_->appendRow(starred_);
    root_->appendRow(inbox_);
  } else {
    // Always reset starred playlist
    // TODO: might be improved by including starred playlist in the response,
    // and reloading it only when needed, like other playlists.
    starred_->removeRows(0, starred_->rowCount());
    LazyPopulate(starred_);
  }

  // Don't do anything if the playlists haven't changed since last time.
  if (!DoPlaylistsDiffer(response)) {
    qLog(Debug) << "Playlists haven't changed - not updating";
    return;
  }
  qLog(Debug) << "Playlist have changed: updating";

  // Remove and recreate the other playlists
  for (QStandardItem* item : playlists_) {
    item->parent()->removeRow(item->row());
  }
  playlists_.clear();

  for (int i = 0; i < response.playlist_size(); ++i) {
    const pb::spotify::Playlists::Playlist& msg = response.playlist(i);

    QString playlist_title = QStringFromStdString(msg.name());
    if (!msg.is_mine()) {
      const std::string& owner = msg.owner();
      playlist_title +=
          tr(", by ") + QString::fromUtf8(owner.c_str(), owner.size());
    }
    QStandardItem* item = new QStandardItem(playlist_title);
    item->setData(InternetModel::Type_UserPlaylist, InternetModel::Role_Type);
    item->setData(true, InternetModel::Role_CanLazyLoad);
    item->setData(msg.index(), Role_UserPlaylistIndex);
    item->setData(msg.is_mine(), InternetModel::Role_CanBeModified);
    item->setData(InternetModel::PlayBehaviour_MultipleItems,
                  InternetModel::Role_PlayBehaviour);
    item->setData(QUrl(QStringFromStdString(msg.uri())),
                  InternetModel::Role_Url);

    root_->appendRow(item);
    playlists_ << item;

    // Preload the playlist items so that drag & drop works immediately.
    LazyPopulate(item);
  }
}