Python na prática: Projeto Video Creator – #Parte 4: Limpeza e interpretação do texto

Bom dia, boa tarde, boa noite!

Essa é a quarta parte da série sobre o projeto Video Creator, onde mostrarei a limpeza e a interpretação do texto.

Se você entrou direto nesse post, te aconselho a começar por aqui, para entender exatamente do que se trata essa série.

Sobre essa parte

Para este projeto, optei por fazer uma limpeza simples, apenas removendo conteúdos dentro de parenteses, uma vez que o foco era gerar um texto que ficasse apresentável em um video.

Após a limpeza, utilizei a lib NLTK para quebrar o texto em sentenças e a API do IBM Watson para identificar as Keywords de cada sentença.

Requisitos:

Para essa etapa do projeto, precisei criar uma conta no IBM Cloud, é gratuito e possui diversos recursos com Free Tier. Vale a pena conferir!

Precisei também dos import de algumas libs:

import re # Regular Expression
from ibm_watson import NaturalLanguageUnderstandingV1
import ibm_watson.natural_language_understanding_v1 as nlu #import Features, EntitiesOptions, KeywordsOptions, 
from ibm_cloud_sdk_core.authenticators import IAMAuthenticator
import nltk # Natural language toolkit

Criando a conta na IBM Cloud e gerando a API Key

Esse passo é bem tranquilo… basta acessar o link abaixo e criar a sua conta:

https://cloud.ibm.com/registration

Após a conta criada, basta logar e você verá uma tela parecida com essa:

layout de 02/2022

Claro que isso pode mudar de acordo com o momento em que você está lendo este post.

De qualquer forma, basta digitar na caixa de busca no topo da página, ou procurar a página de recursos e procurar por: Natural Language Understanding:

Feito isso, agora só precisa acessar a página do recurso, e salvar as credenciais na pasta do seu projeto.

Eu criei uma pasta credentials e dentro dela criei um arquivo chamado credentials.yml

Feito isso, voltamos para o código:

A limpeza e a interpretação do texto, eu mantive dentro do robô de conteúdo, para que ao chamarmos, ele faça tudo o que precisa com a parte textual e deixe-o pronto para ser usado.

A higienização do texto defini da seguinte forma:

def clear_text(text):
    logging.info('Cleaning text...')
    print ("Limpando texto...", end="\n\n")
    return re.sub(r"\([^()]*\)", '', text)

NLTK e IBM Watson em ação!

Para a criação das sentenças e as keywords de cada sentença, encapsulei tudo dentro da função create_sentences_from_text().

A função ficou assim:


'''
Create sentences from text
'''
def create_sentences_from_text(cleaned_content):
    '''
    Get Keywords from a Sentece
    '''
    def get_keywords_from_sentence(sentence):
        # params from Watson API
        authenticator = IAMAuthenticator(credentials['nlu_watson_api_key'])
        service = NaturalLanguageUnderstandingV1(version='2021-09-26',authenticator=authenticator)
        service.set_service_url(credentials['nlu_watson_url'])
        response = service.analyze(
            text=sentence,
            features=nlu.Features(
                            keywords=nlu.KeywordsOptions())
        ).get_result()
        
        # returning just the list of keywords when relevance > 0.5
        return [d['text'] for d in response['keywords'] if d['relevance'] > 0.5]
    
    # creating sentences
    sentences = nltk.tokenize.sent_tokenize(cleaned_content)[:MAX_SENTENCES_TO_FETCH]
    list_sentences = []

    for s in sentences:
        try:
            # analyzing the content with watson
            keywords = get_keywords_from_sentence(s)
        except Exception as ex_analyze:
            print(ex_analyze)

        list_sentences.append({
            'text': s,
            'keywords': keywords,
            'images': []
        })

    return list_sentences

Notem que dentro dessa função existe uma outra chamada get_keywords_from_sentence(), ela é responsável por gerar as keywords das sentenças. Pra isso, fiz uso da Natural Language Understanding da lib IBM_WATSON.

Bom, nesse momento eu já tinha todos os pedaços necessários para gerar o conteúdo textual do video. Era hora de salvar tudo em algum lugar, e seguir para os próximos robôs.

No próximo post, te mostro como fiz isso!

Abraço!

Python na prática: Projeto Video Creator – #Parte 3: Buscando o assunto escolhido na Wikipedia

Bom dia, boa tarde, boa noite!

Essa é a terceira parte da série sobre o projeto Video Creator, onde mostrarei a escolha pelo assunto, e a busca na Wikipedia.

Se você entrou direto nesse post, te aconselho a começar por aqui, para entender exatamente do que se trata essa série.

Sobre essa parte

Para essa versão inicial do projeto, segui a idéia do projeto original e minha fonte de dados textuais será apenas o Wikipedia. Mas já tenho algumas idéias pra evolução desse projeto :D.

Requisitos:

O consumo da API da Wikipedia também é bastante simples, precisei apenas da lib requests:

import requests

A URL que iremos consumir é a seguinte:

https://{language}.wikipedia.org/w/api.php?action=query&format=json&prop=extracts&exintro=1&exlimit=1&titles={subject}&explaintext=1&formatversion=2

Onde {language} é o idioma, neste caso coloquei como uma variável, pois é possível trocar e escolher outros idiomas. E {subject} é o assunto que iremos pesquisar.

A function que busca o conteúdo na wikipedia ficou assim:

def get_wikipedia_content(subject, lang):
    logging.info("Searching about the content choosen on wikipedia")
    print("")
    print("---------------------------------------------------------------------------------------------")
    print("Buscando conteúdo na wikipedia para assunto escolhido: {}".format(subject), end="\n\n")
    try:
        # main subject
        #url = "https://{}.wikipedia.org/api/rest_v1/page/summary/{}".format(lang, subject)
        main_url = WIKIPEDIA_MAIN_URL.format(lang, urllib.parse.unquote(subject))
        main_content = requests.get(main_url)
        main_content_json = main_content.json()
        main_result = main_content_json['query']['pages'][0]
        if  'missing' in main_content_json:
            return main_content_json['missing']
        # media
        media_url = "https://{}.wikipedia.org/api/rest_v1/page/media-list/{}?redirect=false".format(lang, subject)
        media_content = requests.get(media_url)
        media_content_json = media_content.json()
        # get images if there is
        if 'items' in media_content_json:
            media_result = media_content_json['items']
            # incluindo imagens no payload principal
            main_result['images'] = media_result
        # removing unnecessary keys
        main_result.pop('pageid', None)
        main_result.pop('ns', None)

        if 'extract' in main_result:
            return main_result['extract']
        else:
            raise ValueError("Conteúdo não encontrado")

    except Exception as ex:
        logging.error(ex)
        print(ex)

Mas como ocorre a escolha do assunto?

Para selecionar um assunto e chamar a function que buscará o conteúdo na wikipedia, eu encapsulei todo esse fluxo dentro de uma function chamada ask_for_a_subject(), que é responsável por buscar os trending topics do Google e do Twitter, mostrar no terminal o primeiro colocado de cada um e ainda uma terceira opção caso eu deseje digitar um assunto.

Abaixo segue como ficou a função:

def ask_for_a_subject():
    try:
        print("Getting google Trends...")
        gsubject = get_google_trends()
        print("Getting Twitter Trends...", end="\n\n")
        tsubject = get_twitter_trends()

        options = {}
        options[1] = gsubject[0]['title']
        options[2] = tsubject[0]['name']
        options[3] = "Outro"

        # log
        logging.info("--- Asking for content ---")
        # define variable
        content_choosen = "Nenhuma opção escolhida"
        print("Escolha uma das opções", end="\n\n")
        for k in options:
            print("{}) {}".format(k, options[k]))
        
        while content_choosen not in options:
            user_input = int(input("Digite o numero do conteúdo desejado: "))
            if user_input in options:
                content_choosen = options[int(user_input)]
                if content_choosen == 'Outro':
                    content_choosen = input("Digite um termo para ser pesquisado: ")
                    break    
                break
            else:
                print("Escolha uma opção válida")
    except Exception as ex:
        return ex

    return str(content_choosen)

Após a escolha do assunto, é solicitado a escolha da categoria desse conteúdo. Essa categoria será usada no upload do video.

A lista de categorias disponíveis coloquei fixo mesmo, dentro do arquivo rconfig.py. Ficou assim:

# FOR YOUTUBE
YOUTUBE_CATEGORY_LIST = [
        (1, 'Film & Animation'),
        (2, 'Autos & Vehicles'),
        (10, 'Music'),
        (15, 'Pets & Animals'),
        (17, 'Sports'),
        (18, 'Short Movies'),
        (19, 'Travel & Events'),
        (20, 'Gaming'),
        (21, 'Videoblogging'),
        (22, 'People & Blogs'),
        (23, 'Comedy'),
        (24, 'Entertainment'),
        (25, 'News & Politics'),
        (26, 'Howto & Style'),
        (27, 'Education'),
        (28, 'Science & Technology'),
        (29, 'Nonprofits & Activism'),
        (30, 'Movies'),
        (31, 'Anime/Animation'),
        (32, 'Action/Adventure'),
        (33, 'Classics'),
        (34, 'Comedy'),
        (35, 'Documentary'),
        (36, 'Drama'),
        (37, 'Family'),
        (38, 'Foreign'),
        (39, 'Horror'),
        (40, 'Sci-Fi/Fantasy'),
        (41, 'Thriller'),
        (42, 'Shorts'),
        (43, 'Shows'),
        (44, 'Trailers')
    ]

A função ficou assim:

def ask_for_a_category():
    # log
    logging.info("--- Asking for a category ---")
    print("Qual a categoria do conteúdo? ", end="\n\n")

    category_choosen = 0
    available_options = []
    for category in YOUTUBE_CATEGORY_LIST:
        print("{}) {}".format(category[0], category[1]))
        available_options.append(category[0])
    
    while category_choosen == 0:
        user_input = int(input("Digite o numero da categoria escolhida: "))
        if user_input in available_options:
            category_choosen = user_input
            break
        else:
            print("Escolha uma opção válida")

    return str(category_choosen)

Uma coisa importante, é que o id da categoria apesar de ser um numero inteiro, na API do Youtube, o atributo category_id espera receber uma string.

Após a escolha da categoria, a função get_wikipedia_content() é invocada passando o assunto escolhido e o idioma:


def get_wikipedia_content(subject, lang):
    logging.info("Searching about the content choosen on wikipedia")
    print("")
    print("---------------------------------------------------------------------------------------------")
    print("Buscando conteúdo na wikipedia para assunto escolhido: {}".format(subject), end="\n\n")
    try:
        # main subject
        #url = "https://{}.wikipedia.org/api/rest_v1/page/summary/{}".format(lang, subject)
        main_url = WIKIPEDIA_MAIN_URL.format(lang, urllib.parse.unquote(subject))
        main_content = requests.get(main_url)
        main_content_json = main_content.json()
        main_result = main_content_json['query']['pages'][0]
        if  'missing' in main_content_json:
            return main_content_json['missing']
        # media
        media_url = "https://{}.wikipedia.org/api/rest_v1/page/media-list/{}?redirect=false".format(lang, subject)
        media_content = requests.get(media_url)
        media_content_json = media_content.json()
        # get images if there is
        if 'items' in media_content_json:
            media_result = media_content_json['items']
            # incluindo imagens no payload principal
            main_result['images'] = media_result
        # removing unnecessary keys
        main_result.pop('pageid', None)
        main_result.pop('ns', None)

        if 'extract' in main_result:
            return main_result['extract']
        else:
            raise ValueError("Conteúdo não encontrado")

    except Exception as ex:
        logging.error(ex)
        print(ex)

No próximo post, mostrarei como fiz para a limpeza e interpretação do texto.

Um abraço e até o próximo post!

Python na prática: Projeto Video Creator – #Parte 2: Idéias de conteúdo

Bom dia, boa tarde, boa noite!

Este é o segundo post da série sobre o projeto Video Creator, onde mostrarei o consumo de RSS do Google trends e a busca pelos Trending topics do Twitter para sugerir conteúdos para o video.

Se você entrou direto nesse post, te aconselho a começar por aqui, para entender exatamente do que se trata essa série.

Sobre essa parte

Para esse projeto, resolvi mostrar apenas o primeiro colocado do Google e do Twitter, porém os serviços retornam bem mais informações que vocês podem consumir e utilizar para personalizar o robô de vocês.

Lembrem-se, esse projeto é apenas uma POC… um start pra coisas maiores.

Requisitos

Essa parte é bastante simples, pois o Google possui um serviço de RSS aberto onde você consegue ver os assuntos mais buscados do dia. E o Twitter possui uma API bastante simples de consumir, onde também faremos uso do de uma requisição simples a uma URL, a diferença é que para o Twitter, precisaremos de uma API KEY.

Para o Google, utilizei a seguinte URL:

https://trends.google.com.br/trends/trendingsearches/daily/rss?geo=BR

Ao consultar a URL, temos o documento XML seguinte:

Isso é muito legal! 😀

Para o Twitter, utilizei URL padrão da api, passando como parâmetro o id referente ao Brasil, assim, o resultado será apenas os trending topics do Brasil:

https://api.twitter.com/1.1/trends/place.json?id=23424768

Então basicamente o que precisei fazer, foi bater nessas URLs e fazer o parser para dentro do meu código para extrair os dados que eu queria, que nesse caso, foi apenas o atributo title do Google Trends, e o atributo name do Trending Topics do primeiro colocado.

Pra isso utilizei as libs requests e xmltodict:

import requests
import xmltodict

Para o Google trends, encapsulei a requisição e o parser na função abaixo:

def get_google_trends():

    def parser_xml(v_xml):
        key_words = []
        # parser
        content_xml = xmltodict.parse(v_xml)

        for i in content_xml['rss']['channel']['item']:
            
            rel_news = []

            for j in i['ht:news_item']:
                
                if not isinstance(j, str):
                    rel_news.append(j['ht:news_item_title'])

            obj = {
                "title": "{}".format(i['title']), 
                "traffic": "{}".format(i['ht:approx_traffic']),
                "pubDate": "{}".format(i['pubDate']),
                "description": "{}".format(i['description']),
                "related_news": rel_news,
                "dateInsert": datetime.datetime.now()
                }
            key_words.append(obj)
        
        return key_words

    logging.info("--- Getting GOOGLE TRENDS ---")
    # URL do trending topics do Google
    url = "https://trends.google.com.br/trends/trendingsearches/daily/rss?geo=BR"

    content = requests.get(url)
    return parser_xml(content.text)

E para o trending topics do Twitter:

def get_twitter_trends():
    logging.info("--- Getting TWITTER TRENDS ---")
    
    headers = {"Authorization": "Bearer {}".format(credentials['api_bearer_token'])}
    url = "https://api.twitter.com/1.1/trends/place.json?id=23424768"
    response = requests.get(url, headers=headers)
    
    content = []
    
    for i in response.json():
        for j in i['trends']:
            content.append({
                "at": datetime.datetime.now(),
                "as_of": i['as_of'],
                "location": i['locations'][0]['name'],
                "name": j['name'],
                "tweet_volume": j['tweet_volume']
            })
    return content

Essas 2 funções são chamadas dentro de uma outra função que detalharei no próximo post onde falarei sobre a busca pelo conteúdo desejado na wikipedia.

Até o próximo post!

Abraços!

Python na prática: Projeto Video Creator – #Parte 1: Arquitetura da aplicação

Bom dia, boa tarde, boa noite!

Este é o primeiro post da série sobre o projeto Video Creator, onde mostrarei a arquitetura que utilizei no projeto, organização das pastas e dos códigos.

Se você entrou direto nesse post, te aconselho a começar por aqui, para entender exatamente do que se trata essa série.

Sobre essa parte

Sempre que vou começar um novo projeto, gosto de começar a pensar como vou estruturar as pastas e os códigos.

Para esse projeto eu usei a seguinte estrutura:

videocreator # Na raíz fica o “orquestrador” dos robôs
├── content # aqui fica todo o conteúdo para o video
│   ├── default # arquivos default
│   ├── images # imagens para o video
│   └── music # musicas para o video
├── credentials # aqui ficam todas as credenciais dos serviços
└── robots # aqui ficam os “robôs”

Organização do código

Na hora de codificar cada uma das features, tentei agrupá-las por “assunto”, afim de que cada robô pudesse ser executado por completo e de forma independente.

São eles:

rcontent.py

O rcontent é responsável por todo o conteúdo textual do vídeo. É ele quem busca os trending topics para nos dar idéias de conteúdo e nos pergunta qual o assunto a ser buscado.

Após a busca pelo assunto escolhido, é ele o responsável por limpar o texto e quebrá-lo em sentenças, além de identificar todas as palavras-chaves de cada sentença.

Por último, ele gera o arquivo content.json e salva na pasta content.

# Estrutura do arquivo content.json

{
    "search_term": "Assunto pesquisado",
    "youtube_details": {
        "category_id": "id da categoria",
        "title": "Título do video",
    "original_content": "Texto original",
    "cleaned_content": "Texto limpo",
    "sentences": [
        {
            "text": "Sentença",
            "keywords": [],
            "images": [],
            "image_search_query": "string usada na pesquisa"
        }
    ],
    "images_used": [],
    "video_filename": "nome_video.mp4"
}

rimage.py

O rimage é responsável pelas imagens que serão utilizadas no video. Ele busca as imagens com base nas keywords de cada sentença, e armazena o link das 5 primeiras no atributo images de cada sentença.

Após a busca pelas imagens, ele inica o processo de download, que funciona da seguinte forma:

É feito uma tentativa de download da primeira imagem, se ele conseguir, ótimo, ele vai para a próxima sentença, senão, ele tenta a segunda imagem e assim sucessivamente.

As imagens que são baixadas são guardadas no atributo images_used.

Por último, ele atualiza o arquivo content.json com esses novos dados.

rvideo.py

Talvez o mais desafiador entre os 4, pois até o momento era a única coisa no projeto que eu ainda não havia feito com programação… a compilação de um video. O rvideo é responsável por editar as imagens baixadas e compilar o video.

E por que a edição das imagens ficou no rvideo ? Porque as imagens da forma que foram editadas, foram feitas para o vídeo, então fazia mais sentido deixar que ele msmo trate as imagens que ele ira utilizar.

Por último, após o video compilado, ele insere o nome do arquivo de video no atributo video_filename, salva o titulo do video no atributo youtube_details:title e atualiza o arquivo content.json.

ryoutube.py

O último robô a ser desenvolvido tem uma tarefa muito simples: fazer o upload do video no youtube.

Ele é reponsável por gerar o título, descrição, setar as tags e todas as configurações necessárias para o youtube, além é claro, de realizar o upload do video.

Iniciando a execução dos robôs

Para orquestrar a execução dos robôs, foi criado um arquivo index.py, onde ele é responsável por chamar a função start() de cada robô.

Por ser o mais simples de todos, deixarei esse por último.

So far So good?

Ainda ficou muito questão a ser melhorada, mas isso será assunto para a segunda versão do robô, onde tratarei da evolução dele, e o que mais podemos fazer.

Nos próximos posts começarei a mostrar como ficou o código

Um abraço, e até o próximo post!

Gerando videos para o Youtube com linguagem Python

Navegando pelo Youtube a mais ou menos 2 anos atrás, me deparei com uma playlist do canal Filipe Deschamps onde ele mostrava como havia programado 4 robôs para automatizar a criação de videos para o Youtube fornecendo apenas o assunto para o robô.

Para quem quiser conferir, esse é o link do primeiro video da série:

Programei 4 robôs que criam vídeos para mim no YouTube

Bom, na época havia começado minha jornada de aprendizado em Python, e resolvi que faria um projeto similar, utilizando Python, e ferramentas alternativas as utilizadas no projeto original.

Pelos meses seguinte, a idéia não saiu do papel, por diversas razões que não precisam ser citadas aqui.

Até que no final de 2021, resolvi que tiraria esse projeto do papel, apenas por diversão e para praticar um pouco de Python… foi bem divertido :D.

O projeto ficou pronto e pode ser conferido no repositório que postarei mais abaixo. Porém não foi fácil concluí-lo. Diversas coisas no projeto segui o exatamente o que ele fez, outras nem tanto. Também utilizei muito do conhecimento passado nos videos do Filipe Deschamps, porém ele usou Node.js e como resolvi fazer em Python, muitas coisas precisei pesquisar como poderia fazer, quais libs poderia usar, etc.

Então para registrar o conhecimento que adquiri com esse pequeno projeto, resolvi escrever uma série de posts, cada qual abordando um pedaço do projeto, assim como o Filipe fez em video.

O objetivo principal é para reforçar, por meio do repasse, o aprendizado adquirido. Espero que estes posts possam ajudar vocês de alguma forma, seja com o conteúdo técnico ou simplesmente motivando-os a iniciarem seus projetos pessoais. É a melhor forma de aprender, e como o Filipe falou nos videos, são nesses projetos, que podemos usar toda e qualquer tecnologia que temos vontade, sem nos preocupar se é viável ou não.

Repositório com o projeto concluído:
https://github.com/thmlima87/videocreator

O que foi usado no projeto?

Apesar de parecer simples, o projeto envolveu bastante coisa legal:

Bora começar!

Abaixo os links de todos os posts para facilitar a navegação.

Parte 1: Arquitetura da aplicação
Parte 2: Idéias de conteúdo!
Parte 3: Buscando o assunto escolhido na Wikipedia
Parte 4: Limpeza e interpretação do texto
Parte 5: Persistência do conteúdo
Parte 6: Bing Custom Search
Parte 7: Busca e download de imagens
Parte 8: Tratamento das imagens
Parte 9: Renderização do video
Parte 10: Upload para o Youtube

Exit mobile version