Criando a foto oficial da Python Brasil 2020
A Python Brasil é o maior encontro da comunidade Python na América Latina. Em 2020, por motivos de COVID-19, pela primeira vez em seus 16 anos, a Python Brasil aconteceu online.
Uma das várias tradições do evento, é a foto oficial. Durante a Python Brasil, geralmente no último dia de palestras, todos os participantes, incluindo palestrantes, keynotes e organização, se juntam para uma foto que marca aquela edição. Esse ano não havíamos planejado nada, já que o evento seria online. Até que, faltando pouco mais de uma hora para o encerramento, um dos membros da organização, André Pastore, relembrou da foto oficial no canal da organização.
Nessa hora, comentei o desafio da foto oficial com os amigos que estavam no canal de voz do #boteco. Após alguns minutos de conversa tivemos a primeira ideia: baixar todas fotos de perfil e enviar para a Camila montar, de alguma forma, a foto oficial (A Camila foi responsável pelo trabalho belíssimo nas artes do evento).
Se você quiser reproduzir os trechos de código que vou mostrar abaixo, comece criando um ambiente virtual e instalando as duas dependências necessárias:
$ python -m venv venv
$ source venv/bin/activate
$ pip install discord.py pillow
Começamos pesquisando se existia algum método na API do Discord que retornasse o que precisávamos. Como eu já estava trabalhando no robô que ajudou a organizar o servidor, as dependências e credenciais necessárias já estavam prontas.
Conseguimos resolver a primeira parte do desafio rapidamente. O Gabriel logo encontrou o atributo avatar_url
que retorna o caminho da foto de perfil do usuário. Inclusive, a própria biblioteca contém o método save
para salvar a imagem em um arquivo.
Seguimos fazendo o primeiro teste para 20 usuários. Checamos o tamanho dos arquivos que foram baixados e, limitamos a função para que fizesse download das imagens de perfil apenas dos usuários que alteraram a foto, deixando de fora a imagem padrão do Discord.
async def salvar_imagem(participante: discord.Member):
if participante.default_avatar_url == participante.avatar_url:
# Se default_avatar_url é igual ao avatar_url, quer dizer que
# a pessoa não alterou a foto de perfil
return
caminho = f"fotos/{participante.id}.webp"
with open(caminho, "wb") as arquivo:
await participante.avatar_url.save(arquivo)
return caminho
async def main():
token = "Token do bot no Discord"
guild = "ID do servidor da Python Brasil"
# Aqui criamos uma instância do client do Discord. Nós não precisávamos
# usar o `discord.Intents.all()`, mas essa parte de autorização é mais
# complexa, então fica para um próximo texto.
cliente = discord.Client(intents=discord.Intents.all())
await cliente.login(token)
# Nessa parte, buscamos o servidor da Python Brasil e listamos todos os
# membros que estavam presentes.
guild = await cliente.fetch_guild(guild)
membros = await guild.fetch_members().flatten()
# Pronto, agora é só chamar a função salvar_imagem para cada um dos membros
tarefas = [salvar_imagem(membro) for membro in membros]
await asyncio.gather(*tarefas)
await client.close()
No fim, baixamos 1330 imagens de perfil dos participantes da Python Brasil 2020. A experiência migrando o rastreiobot para asyncio
ajudou bastante, já que os métodos da biblioteca do discord.py, que fazem requisições para a API, também usam asyncio
.
Em posse das 1330 fotos, ficou claro que pedir para a Camila montar, manualmente, a foto oficial era algo inviável. Ainda mais porque o encerramento já estava próximo. Mais alguns minutos de conversa sobre as possibilidades, decidimos tentar juntar todas as fotos em uma só, usando o Pillow, a principal biblioteca de processamento de imagem em Python.
A essa altura, várias pessoas já estavam acompanhando o canal de voz, o que foi crucial, já que mais gente pôde ajudar. Por exemplo, Mazza e Gabriel encontraram a proporção correta da imagem final, Pastore achou o esqueleto do código usando Pillow, eu juntei tudo em um script.py e o João Bueno (JS) afinou os últimos detalhes. O resultado foi a função abaixo:
def gerar_imagem():
# As imagens vieram do Discord com 1024 pixels de largura e altura.
# Logo, o primeiro passo é redimensioná-las para 100 pixels de largura e altura.
imagens = [
Image.open(foto).resize((100, 100), Image.ANTIALIAS)
for foto in Path("fotos").iterdir()
]
# Aqui criamos a estrutura da imagem final, com 4800 pixels de largura e
# 2800 pixels de altura.
imagem_final = Image.new('RGB', (4800, 2800))
for x in range(0,48):
for y in range(0,28):
# A parte `% len(imagens)` faz com que o laço reuse imagens,
# caso a proporção não seja perfeita.
indice = (48 * y + x) % len(imagens)
# Agora preenchemos a imagem final com cada uma das imagens de perfil
# dos participamentes.
imagem_final.paste(imagens[indice], (x * 100, y * 100))
imagem_final.save("python-brasil-2020.jpg")
Durante a conversa, vimos alguns pontos que poderíamos melhorar o script, como por exemplo redimencionar a imagem dentro do laço, diminuindo o consumo de memória. Se você tiver alguma sugestões, comente abaixo.
A foto oficial da Python Brasil 2020, resultado de um processo colaborativo, sinônimo da comunidade, pode ser visto na melhor qualidade aqui.
- Site da Python Brasil 2020: https://2020.pythonbrasil.org.br.
- Canal no YouTube com as palestras, mesas redondas e keynotes: https://www.youtube.com/c/pythonbrasiloficial
- Script completo: https://gist.github.com/rougeth/0342f2982472323569cbe7fbf73be474
Revisões: Leticia Portella e André Pastore.
o/