06/07/2024

Tenho algumas centenas de horas de aulas gravadas ensinando algoritmos e estruturas de dados, padrões de projeto, Domain-driven Design e Reputação e Marketing Pessoal. Também tenho algumas centenas de horas de sessões de mentoria em arquitetura de software.

Nas minhas masterclasses, sempre vou muito além do material preparado.

Todos esses vídeos possuem ideias incríveis, mas que não são fáceis de acessar, sem que, obviamente, alguém assista todos os vídeos novamente, estruturando conteúdo de maneira interessante.

Recentemente, a Google lançou uma ferramenta incrível chamada NotebookLM. Ela permite “conversar com documentos”. Mas, até então, estes documentos precisam ser textos.

Então, o caminho tem sido gerar versões em texto dos meus vídeos. Mas, como? A resposta é utilizar tecnologias de IA como a Whisper da OpenAI.

Preparação do Ambiente

Para gerenciar as dependências de forma eficaz, recomendo criar um ambiente virtual usando conda. Primeiro, instale a última versão do Python e configure o ambiente:

Python
conda create -n transcricao_env python=3.10
conda activate transcricao_env

Instalação dos Pacotes

Com o ambiente configurado, instale os pacotes necessários:

Python
!pip install git+https://github.com/openai/whisper.git
!pip install ffmpeg-python

Passo-a-passo (Jupyter Notebook)

Importação e Carregamento do Modelo

Em um notebook (ou script Python), começo importando os pacotes necessários e carregando o modelo do Whisper. A primeira execução consome um pouco mais de tempo:

Python
import whisper
import ffmpeg
import os

model = whisper.load_model("base")

Extração do Áudio do Vídeo

Em seguida, extraímos o áudio do vídeo. Isso é feito para facilitar a transcrição, uma vez que o Whisper trabalha diretamente com arquivos de áudio:

Python
video_path = r"/Users/elemarjr/Downloads/mentoria_arquitetura_1.mp4"
audio_path = r"/Users/elemarjr/Downloads/mentoria_arquitetura_1.m4a"
ffmpeg.input(video_path).output(audio_path).run(overwrite_output=True)

Geração da Transcrição

Finalmente, obtemos a transcrição e a salvamos em um arquivo de texto:

Python
# Obter a transcrição
output_path = r"/Users/elemarjr/Downloads/mentoria_arquitetura_1.txt"
result = model.transcribe(audio_path, language='pt')

# Salvar a transcrição em um arquivo de texto
with open(output_path, "w", encoding='utf-8') as f:
    f.write(result['text'])

Pronto para o NotebookLM

Pronto. Tenho um arquivo texto com todo o conteúdo da aula que, agora, pode ser usado como source no NotebookLM.

Juntando tudo em um Utilitário de Linha de Comando

Para facilitar o uso do processo descrito acima, criei um utilitário de linha de comando em Python. Este script permite passar o caminho de um vídeo como argumento e obtém a transcrição do vídeo. Aqui está o código:

Python
import whisper
import ffmpeg
import os
import sys

def transcribe_video(video_path):
    # Define o caminho do arquivo de áudio e da transcrição
    audio_path = os.path.splitext(video_path)[0] + '.m4a'
    output_path = os.path.splitext(video_path)[0] + '.txt'

    # Extrai o áudio do vídeo
    ffmpeg.input(video_path).output(audio_path).run(overwrite_output=True)

    # Carrega o modelo Whisper
    model = whisper.load_model("base")

    # Gera a transcrição
    result = model.transcribe(audio_path, language='pt')

    # Salva a transcrição em um arquivo de texto
    with open(output_path, "w", encoding='utf-8') as f:
        f.write(result['text'])

    print(f"Transcrição salva em {output_path}")

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Uso: python transcribe.py <caminho_para_o_video>")
        sys.exit(1)

    video_path = sys.argv[1]
    transcribe_video(video_path)

Para usar este utilitário, salve o código acima em um arquivo chamado transcribe.py e execute-o na linha de comando passando o caminho para o vídeo:

python transcribe.py /caminho/para/o/video.mp4

Pronto! Agora você tem um utilitário de linha de comando que transcreve vídeos automaticamente.

Possíveis Erros e Soluções

Durante o processo, alguns erros podem ocorrer. Aqui estão alguns comuns e como resolvê-los:

  • Erro de instalação de pacotes: Certifique-se de que você tem permissão para instalar pacotes e que sua conexão com a internet está estável.
  • Erro ao carregar o modelo: Verifique se você tem espaço suficiente em disco e memória disponível.
  • Erro ao extrair o áudio: Assegure-se de que o caminho do arquivo de vídeo está correto e que o ffmpeg está instalado corretamente.

Contextualização e Aplicações Práticas

A transcrição de áudio e vídeo é extremamente útil para diversas aplicações, como:

  • Análise de conteúdo: Facilita a análise de grandes volumes de informações contidas em vídeos.
  • Acessibilidade: Ajuda na criação de legendas, tornando o conteúdo acessível para pessoas com deficiência auditiva.
  • SEO e Indexação: Melhora o SEO, permitindo que os motores de busca indexem o conteúdo textual.
  • Recuperar conteúdo relevante e contextualizado: Permite encontrar rapidamente trechos importantes e contextualmente ricos dos vídeos gravados.

Com essa abordagem, posso oferecer material de qualidade para quem estuda comigo, transformando horas de vídeo em documentos de texto utilizáveis, prontos para serem analisados e manipulados em ferramentas como o NotebookLM.

Se você deseja estudar comigo e acessar material de alta qualidade e contextualizado, entre em contato. Estou sempre pronto para compartilhar conhecimento e ajudar no seu desenvolvimento profissional.

01/05/2024

WordPress é uma das plataformas mais populares do mundo para construção de Websites e blogs. A Ontologia da EximiaCo foi construída com WordPress.

Os consultores da EximiaCo colaboram com a produção de conteúdo que está devidamente organizado em classes.

Buscando dados do WordPress

O WordPress disponibiliza uma API RESTish que permite obtenção fácil de informações a respeito de um site.

Python
import pandas as pd
import requests
from cachetools import cached, TTLCache
from datetime import datetime


class WordPress:
    def __init__(self, url):
        self.url = url

    @cached(cache=TTLCache(maxsize=100, ttl=300))
    def get_post_types(self):
        endpoint = '/wp-json/wp/v2/types'
        api_url = f'{self.url}{endpoint}'

        response = requests.get(api_url)
        post_types = response.json()

        if response.status_code == 200:
            data = [{'key': key, 'name': value['name']} for key, value in post_types.items()]
            return pd.DataFrame(data)
        else:
            return pd.DataFrame()

    @cached(cache=TTLCache(maxsize=100, ttl=300))
    def get_users(self):
        api_url = f'{self.url}/wp-json/wp/v2/users'

        response = requests.get(api_url)

        if response.status_code == 200:
            users_data = response.json()
            df = pd.DataFrame(users_data)
            return df
        else:
            return pd.DataFrame()

    @cached(cache=TTLCache(maxsize=100, ttl=300))
    def get_posts(self, post_type_slug, start_date, end_date):

        start_iso = (datetime.strptime(start_date, '%Y-%m-%d')
                     .replace(hour=0, minute=0, second=0)
                     .strftime('%Y-%m-%dT%H:%M:%S'))
        end_iso = (datetime.strptime(end_date, '%Y-%m-%d')
                   .replace(hour=23, minute=59, second=59)
                   .strftime('%Y-%m-%dT%H:%M:%S'))

        endpoint = f'/wp-json/wp/v2/{post_type_slug}'
        api_url = f'{self.url}{endpoint}'

        params = {
            'after': start_iso,
            'before': end_iso,
            'date_gmt': ''
        }

        response = requests.get(api_url, params=params)
        posts = response.json()

        if response.status_code == 200 and isinstance(posts, list):
            data = [
                {'ID': post['id'],
                 'Title': post['title']['rendered'],
                 'Modified': post['modified'],
                 'Created': post['date_gmt'],
                 'AuthorId': post.get('author', 1)
                 } for post in posts]
            df = pd.DataFrame(data)
            return df
        else:
            return pd.DataFrame()

Tenho optado por retornar Dataframes do Pandas quase sempre. Isso torna a interação com os dados algo fácil.

Outra coisa que resolvi fazer nessa implementação foi utilizar um cache em memória mesmo. A ideia é reduzir a quantidade de “ataques” no servidor para informações que considero estáveis.

Facilitando as pesquisas envolvendo períodos semanais

Boa parte das consultas que irei fazer contra meu WordPress serão relacionadas a períodos semanais – ou seja, começando em um domingo e encerrando no próximo sábado. Para tornar a especificação desses intervalos mais fácil, criei uma classe helper.

Python
from datetime import timedelta, datetime


class Weeks:
    @staticmethod
    def get_week_dates(reference_date):
        start_of_week = reference_date - timedelta(days=reference_date.weekday() + 1)
        end_of_week = start_of_week + timedelta(days=6)
        return start_of_week.strftime('%Y-%m-%d'), end_of_week.strftime('%Y-%m-%d')

    @staticmethod
    def get_current():
        today = datetime.now()
        return Weeks.get_week_dates(today)

    @staticmethod
    def get_previous(n = 1):
        today = datetime.now()
        last_week_date = today - timedelta(days=7 * n)
        return Weeks.get_week_dates(last_week_date)

    @staticmethod
    def get_week_string_from_date(reference_date):
        start, end = Weeks.get_week_dates(reference_date)
        return Weeks.get_week_string_from_interval(start, end)

    @staticmethod
    def get_week_string_from_interval(start, end):
        star_f = datetime.fromisoformat(start).strftime('%d/%m')
        end_f = datetime.fromisoformat(end).strftime('%d/%m')
        return f'{star_f} - {end_f}'

Adicionando semântica

O WordPress é uma plataforma de propósito geral. A classe que desenvolvi consegue lidar bem com qualquer WordPress, mas deixa um bocado de interpretações a cargo do desenvolvedor.

Quase sempre, considero criar uma “camada semântica” sobre classes de propósito geral como a que desenvolvemos, adicionando significado específico para o contexto (domínio) em que estou trabalhando.

Python
import pandas as pd
from datetime import datetime

from src.wordpress import WordPress
from src.weeks import Weeks

class Ontologia:
    def __init__(self):
        self.wp = WordPress('https://ontologia.eximia.co')

    def get_classes(self):
        df = self.wp.get_post_types()
        classes = df[
            ~(df['key'].str.startswith('wp_') |
              df['key'].str.startswith('nav_') |
              df['key'].str.contains('jet-engine') |
              df['key'].str.contains('assets') |
              df['key'].str.contains('classes') |
              df['key'].str.contains('page') |
              df['key'].str.contains('post') |
              df['key'].str.contains('links') |
              df['key'].str.contains('media') |
              df['key'].str.contains('attachment') |
              df['key'].str.contains('visibility_preset') |
              df['key'].str.endswith('_links') |
              df['key'].str.endswith('-links')
              )
        ]
        return classes

    def get_authors(self):
        df = self.wp.get_users()
        return df[~(df['name'].str.contains('admin'))]

    def get_entries(self, start_date, end_date):
        classes_df = self.get_classes()
        entries_list = []

        for index, row in classes_df.iterrows():
            post_type_slug = row['key']
            posts_df = self.wp.get_posts(post_type_slug, start_date, end_date)

            posts_df['ClassKey'] = row['key']
            posts_df['ClassName'] = row['name']

            entries_list.append(posts_df)

        final_df = pd.concat(entries_list, ignore_index=True)
        final_df = final_df[~(final_df['AuthorId'] == 1)]

        user_name_map = self.get_authors().set_index('id')['name'].to_dict()
        final_df['AuthorName'] = final_df['AuthorId'].map(user_name_map)
        return final_df

    def get_summary_by_author(self, start_date, end_date):
        entries_df = self.get_entries(start_date, end_date)
        summary_df = entries_df.groupby('AuthorName').size().reset_index(name='Count')

        start_date_formatted = pd.to_datetime(start_date).strftime('%d/%m')
        end_date_formatted = pd.to_datetime(end_date).strftime('%d/%m')
        date_column_title = f"{start_date_formatted} - {end_date_formatted}"

        summary_df.columns = ['Author', date_column_title]

        return summary_df

    def get_summary_by_class(self, start_date, end_date):
        entries_df = self.get_entries(start_date, end_date)
        summary_df = entries_df.groupby('ClassName').size().reset_index(name='Count')

        start_date_formatted = pd.to_datetime(start_date).strftime('%d/%m')
        end_date_formatted = pd.to_datetime(end_date).strftime('%d/%m')
        date_column_title = f"{start_date_formatted} - {end_date_formatted}"

        summary_df.columns = ['Class', date_column_title]

        return summary_df

    def get_weekly_summary_by(self, by, alias, number_of_weeks):
        start, _ = Weeks.get_previous(number_of_weeks)
        _, end = Weeks.get_current()

        entries = self.get_entries(start, end)

        def week(row):
            ref = datetime.fromisoformat(row["Created"])
            return Weeks.get_week_string_from_date(ref)

        entries["Week"] = entries.apply(week, axis=1)

        result = pd.DataFrame(columns=[alias])

        for i in range(number_of_weeks):
            start, end = Weeks.get_previous(i)
            week = Weeks.get_week_string_from_interval(start, end)
            df = entries[entries['Week'] == week]

            summary_df = df.groupby(by).size().reset_index(name='Count')
            summary_df.columns = [alias, week]
            result = pd.merge(summary_df, result, on=alias, how='outer')

        result.fillna(0, inplace=True)

        return result

Aqui, a classe semântica substitui a ideia de “usuários” por “autores”, “tipos de postagem” por “classes”, “postagem” por “entradas”.

Além disso, adicionei algumas consultas de sumarização.

23/04/2024

Sempre digo que os repositórios são as “redes sociais” dos desenvolvedores. Nos repositórios, desenvolvedores compartilham código, acessam o código de outras pessoas. Mais do que isso, em uma organização de engenharia, repositórios são a “fonte da verdade”.

Recentemente, desenvolvi um notebook para que os consultores da EximiaCo fizessem análises sobre repositórios. A ideia era identificar arquivos modificados com maior frequência e os contribuidores mais ativos. Para isso, utilizei uma biblioteca, bem bacana, chamada GitPython.

Python
! pip install GitPython --quiet

Abaixo uma método de apoio que retorna a relação de modificações em arquivos realizadas em um repositório nos últimos três meses.

Python
import git
from datetime import datetime, timedelta

def get_commits_last_three_months(repo_path, branch_name):
    repo = git.Repo(repo_path)
    three_months_ago = datetime.now() - timedelta(days=90)
    
    commits = []

    # Iterar sobre os commits na branch especificada
    for commit in repo.iter_commits(branch_name, since=three_months_ago.isoformat()):
        for file_stat in commit.stats.files.items():
            file_name, stats = file_stat
            entry = safe_get(commit.tree, file_name)
            lines = entry.data_stream.read().count(b'n') + 1 if entry else 0
            commit_info = {
                'sha': commit.hexsha,
                'author': commit.author.name,
                'file': file_name,
                'changes': stats['lines'],
                'insertions' : stats['insertions'],
                'deletions': stats['deletions'],
                'bytes': entry.size if entry else 0,
                'lines': lines
            }
            commits.append(commit_info)
            
    return commits

O código em si é bastante simples. Além das informações que a própria biblioteca fornece, resolvi calcular também o número de linhas de cada arquivo em um commit específico.

A ideia completa de como utilizo esse código está no notebook.

Últimos posts

Inscrição realizada com sucesso!

No dia da masterclass você receberá um e-mail com um link para acompanhar a aula ao vivo. Até lá!

A sua subscrição foi enviada com sucesso!

Aguarde, em breve entraremos em contato com você para lhe fornecer mais informações sobre como participar da mentoria.

Crie sua conta

Preencha os dados para iniciar o seu cadastro no plano anual do Clube de Estudos:

Crie sua conta

Preencha os dados para iniciar o seu cadastro no plano mensal do Clube de Estudos:

× Precisa de ajuda?