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
}
Beispiel #14
0
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();
  }
}