Video Creator – #Parte 10: Upload do video para o Youtube

Bom dia, boa tarde, boa noite!

Essa é a décima e última parte da série sobre o projeto Video Creator, onde mostrarei como fiz o upload automático do video no o Youtube.

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 fazer o upload do video no Youtube, usei apenas algumas configurações básicas, mas vale muito a pena dar uma olhada na documentação da API, porque tem muita opção para configurar. Para esse projeto, segui o mais básico.

Requisitos:

Para essa parte, precisaremos habilitar a API do Youtube na sua conta Google Cloud, baixar o arquivo com a secret key que será gerada, fazer alguns imports e está quase tudo pronto. Isso porque 90% do código já está pronto no próprio site da API, e é ele que vamos usar como base:

https://developers.google.com/youtube/v3/guides/uploading_a_video#Sample_Code

Habilitar API do Youtube:

Visite o link abaixo e entre na sua conta, ou crie uma, é gratuíto é tão simples quando criar qualquer outra conta de serviço web.

https://cloud.google.com/

Após logado, você precisará criar um novo projeto, ao lado de Google Cloud Platform, clique no menu dropdown e a modal abaixo aparecerá, clique em Novo Projeto, preencha com o nome desejado e clique em criar. Se o projeto não ficar selecionado automaticamente, clique novamente no menu dropdown e selecione o projeto recém criado.

layout fev/2022

Agora clique nas barrinhas horizontais no canto superior esquerdo da tela, depois vá em APIs e serviços, e por fim selecione Biblioteca.

layout fev/2022

Na tela que abrir, selecione Youtube Data Api v(x):

Na tela que abrir, clique no botão ativar, e sua tela ficará assim:

layout fev/2022

Para visitar a documentação da API, clique em TESTAR ESTA API.

Clique em Gerenciar, e na tela que abrir, selecione a aba Credenciais e depois em Criar credenciais.

layout fev/2022

Selecione ID do cliente OAuth.

Em tipo de aplicativo, selecione Aplicativo da WEB:

layout fev/2022

Digite um nome, e depois em criar, aparecerá um modal com seu ID de cliente e Chave secreta de cliente. Você pode salvar esses dados em um arquivo de config, ou fazer como eu fiz, que é na listagem de credenciais, clicar no botão de download, e baixar o arquivo com esses dados.

Hora de codificar:

Com a API ativada e o arquivo com a secret key já salvo na pasta credentials, é hora de pegar o sample code e fazer algumas personalizações.

O robô inicia com a função start(), que carrega o conteúdo do arquivo content.json para dentro do objeto video_content, cria o objeto youtube, e nesse momento, o navegador abrirá perguntando para qual conta do Youtube você deseja fazer o upload. Após isso, ele chama a função create_thumbnail(), que cria as thumbnails e inicia o processo de Upload.

A função start() ficou assim:


def start():
    logging.info('--- Starting Youtube robot ---')
    video_content = rcontent.load()
    youtube = get_authenticated_service()
    try:
        create_thumbnail(video_content)
        initialize_upload(youtube, video_content)
        result = "Upload concluído!"
    except HttpError as e:
            result = "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content)
    except Exception as ex:
        result = ex
    print(result)

A função create_thumbnail() ficou assim:


def  create_thumbnail(video_content):
    logging.info("Creating Thumbnail...")

    # apagando as thumbs existentes
    os.system("rm -rf {}/thumb*".format(CONTENT_IMAGES_PATH))

    # criando os comandos para execução.
    # Importante: Refatorar para que a montagem do comando esteja dentro de um for

    command_thumb_default = "convert -size {}x{} -font helvetica -pointsize 24 -background 'black' -fill white -gravity center caption:'{}' ./content/images/thumb_default.png"\
        .format(YOUTUBE_VIDEO_THUMBNAILS['default']['width'],YOUTUBE_VIDEO_THUMBNAILS['default']['height'], video_content['youtube_details']['title'])
        
    command_thumb_medium = "convert -size {}x{} -font helvetica -pointsize 26 -background 'black' -fill white -gravity center caption:'{}' ./content/images/thumb_medium.png"\
        .format(YOUTUBE_VIDEO_THUMBNAILS['medium']['width'],YOUTUBE_VIDEO_THUMBNAILS['medium']['height'], video_content['youtube_details']['title'])

    command_thumb_high = "convert -size {}x{} -font helvetica -pointsize 30 -background 'black' -fill white -gravity center caption:'{}' ./content/images/thumb_high.png"\
        .format(YOUTUBE_VIDEO_THUMBNAILS['high']['width'],YOUTUBE_VIDEO_THUMBNAILS['high']['height'], video_content['youtube_details']['title'])
    
    command_thumb_standard = "convert -size {}x{} -font helvetica -pointsize 50 -background 'black' -fill white -gravity center caption:'{}' ./content/images/thumb_standard.png"\
        .format(YOUTUBE_VIDEO_THUMBNAILS['standard']['width'],YOUTUBE_VIDEO_THUMBNAILS['high']['height'], video_content['youtube_details']['title'])

    command_thumb_maxres = "convert -size {}x{} -font helvetica -pointsize 70 -background 'black' -fill white -gravity center caption:'{}' ./content/images/thumb_maxres.png"\
        .format(YOUTUBE_VIDEO_THUMBNAILS['maxres']['width'],YOUTUBE_VIDEO_THUMBNAILS['high']['height'], video_content['youtube_details']['title'])

    os.system(command_thumb_default)
    os.system(command_thumb_medium)
    os.system(command_thumb_high)
    os.system(command_thumb_standard)
    os.system(command_thumb_maxres)

Uma informação importante, é que nessa função eu faço todas as possíveis thumbnails que o Youtube permite, porém não consegui fazer funcionar o upload das thumbnails junto com o video. Precisei utilizar a função set do objeto thumbnails após o upload de video.

Abaixo, as 2 funções que fazem o processo de upload do video:


def initialize_upload(youtube, video_content):
    list_of_used_images = '\n'.join('✅ ' + img for img in video_content['images_used'])
    list_of_sentences = '\n'.join([ s['text'] for s in video_content['sentences']])
    YOUTUBE_VIDEO_DESCRIPTION = """
Conheça um pouco mais sobre {}:

{}

👉 Referências:

💥 Wikipedia
💥 Custom Search API - Bing.

👉 Imagens utilizadas no vídeo:

{}
""".format(video_content['search_term'], list_of_sentences, list_of_used_images)

    YOUTUBE_VIDEO_FILE = CONTENT_PATH + '/{}'.format(video_content['video_filename'])
        
    body= {
        "snippet": {
            "title": video_content['youtube_details']['title'],
            "description": YOUTUBE_VIDEO_DESCRIPTION,
            "tags": get_tags(video_content),
            "categoryId": video_content['youtube_details']['category_id']
        },
        "status": {
            "privacyStatus": YOUTUBE_VIDEO_PRIVACY_STATUS
        }
    }
    # Call the API's videos.insert method to create and upload the video.
    insert_request = youtube.videos().insert(
    part=",".join(body.keys()),
    body=body,
    # The chunksize parameter specifies the size of each chunk of data, in
    # bytes, that will be uploaded at a time. Set a higher value for
    # reliable connections as fewer chunks lead to faster uploads. Set a lower
    # value for better recovery on less reliable connections.
    #
    # Setting "chunksize" equal to -1 in the code below means that the entire
    # file will be uploaded in a single HTTP request. (If the upload fails,
    # it will still be retried where it left off.) This is usually a best
    # practice, but if you're using Python older than 2.6 or if you're
    # running on App Engine, you should set the chunksize to something like
    # 1024 * 1024 (1 megabyte).
    media_body=MediaFileUpload(YOUTUBE_VIDEO_FILE, chunksize=-1, resumable=True)
    )
    insert_response = resumable_upload(insert_request)
    # set thumbnail
    set_thumbnail(youtube, insert_response['id'])

    
# This method implements an exponential backoff strategy to resume a
# failed upload.
def resumable_upload(insert_request):
    response = None
    error = None
    retry = 0
    while response is None:
        try:
            print ("Uploading file...")
            status, response = insert_request.next_chunk()
            if response is not None:
                if 'id' in response:
                    print ("Video id '%s' was successfully uploaded." % response['id'])
                else:
                    exit("The upload failed with an unexpected response: %s" % response)
        except HttpError as e:
            if e.resp.status in RETRIABLE_STATUS_CODES:
                error = "A retriable HTTP error %d occurred:\n%s" % (e.resp.status,e.content)
            else:
                raise
        except RETRIABLE_EXCEPTIONS as e:
            error = "A retriable error occurred: %s" % e

        if error is not None:
            print (error)
            retry += 1
            if retry > MAX_RETRIES:
                exit("No longer attempting to retry.")
            max_sleep = 2 ** retry
            sleep_seconds = random.random() * max_sleep
            print ("Sleeping %f seconds and then retrying..." % sleep_seconds)
            time.sleep(sleep_seconds)

    return response

Dentro da primeiro função initialize_upload(), comecei montando os dados que eu precisaria para fazer o upload do video, utilizei o assunto pesquisado, as sentenças e a lista de imagens usadas para compor a descrição do video e dar os devidos créditos para os sites detentores das imagens usadas.

Na hora de montar as tags, utilizei as keywords das sentenças para. Para isso, criei uma função para buscar as keywords e devolvê-las numa lista.

Ficou assim:


def get_tags(video_content):
    keywords = []

    for sentence in video_content['sentences']:
        for k in sentence['keywords']:
            if k not in keywords:
                keywords.append(k)
    
    return keywords

E após o upload, faço o set da thumbnail:


def set_thumbnail(youtube, video_id):
    print('Setting the thumbnail: {} for video: {}'.format(CONTENT_IMAGES_PATH + "/thumb_maxres.png", video_id))
    youtube.thumbnails().set(
        videoId=video_id,
        media_body=CONTENT_IMAGES_PATH + "/thumb_maxres.png"
    ).execute()

É isso!

Conclusão!

E assim encerro essa série de posts sobre o projeto Video-Creator. Muito feliz com o resultado final.

Sei que tem muitas coisas pra refatorar, pra evoluir, muitas oportunidades de novas implementações, mas o objetivo principal foi atingido: Receber um assunto e gerar um video para o Youtube automaticamente.

Espero que tenham gostado da série, qualquer dúvida, sugestão ou crítica, por favor entrem em contato. Se tiver algum assunto relacionado a essa série de posts que vocês queiram que eu me aprofunde um pouco mais na explicação, mandem um comentário pedindo, prometo trazer assim que possível.

Um grande abraço!

Video Creator – #Parte 9: Renderização de video com Python

Bom dia, boa tarde, boa noite!

Essa é a nona parte da série sobre o projeto Video Creator, onde mostrarei como fiz a renderização do 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 renderização do video, utilizei uma lib fantástica chamada MoviePy.

A lib possui diversas funcionalidades legais, e pra quem quiser saber mais, recomendo dar uma olhada no link abaixo:

https://pypi.org/project/moviepy/

Requisitos:

Para a renderização do video, precisaremos importar as libs do MoviePy.

from moviepy.editor import *
from moviepy.video.tools.segmenting import findObjects

MoviePy:

O MoviePy utiliza algumas features do ImageMagick, e precisamos fazer alguns ajustes nas configurações.

No linux, o MoviePy detecta automaticamente o Image Magick. No windows é preciso criar uma variável de ambiente chamada IMAGEMAGICK_BINARY contendo o caminho do binário. Como utilizei Ubuntu, não tive nenhum problema quanto a isso.

Para que ele funcione corretamente, é preciso configurar uma política de segurança, do contrário, dará erro na hora de compilar o video.

Como isso é somente uma POC, apenas comentei no arquivo /etc/ImageMagick-x/policy.xml algumas linhas:

<!--<policy domain="path" rights="none" pattern="@*"/>-->
<!--<policy domain="resource" name="width" value="16KP"/>-->
<!--<policy domain="resource" name="height" value="16KP"/>-->

Setei algumas variáveis no arquivo rconfig.py:


# FOR VIDEO
VIDEO_INTRODUCTION_TEXT = "Variavel Constante\napresenta..."
VIDEO_SCREENSIZE = (1920,1080)
VIDEO_TEXT_COLOR_DEFAULT = 'white'
VIDEO_TEXT_FONT_DEFAULT = 'helvetica'
VIDEO_FONT_SIZE_DEFAULT = 50
VIDEO_TEXT_POSITION_DEFAULT = 'center'
VIDEO_FRAME_DURATION_DEFAULT = 4
VIDEO_TEXT_KERNING_DEFAULT = 5
VIDEO_CODEC_DEFAULT = 'mpeg4'
VIDEO_FPS_DEFAULT = 30

A parte de compilação do video, encapsulei dentro de uma função, que chamei de compile_video(). Ela ficou assim:


def compile_video():
    logging.info("Compiling video...")

    
    #os.chdir('./')
    path = CONTENT_IMAGES_PATH
    
    # create directory
    video_content = rcontent.load()

    video_filename = util.remove_accents(video_content['search_term']).replace(" ", "_") + ".mp4"
    video_content['video_filename'] = video_filename
    video_content['youtube_details']['title'] = "Um pouco sobre {}".format(video_content['search_term'])

    rcontent.save(video_content)
    # carregando musica
    music = AudioFileClip('{}/gypsy-jaxx.mp3'.format(CONTENT_SOUND_PATH))
    new_audioclip = CompositeAudioClip([music])
    
    screensize = VIDEO_SCREENSIZE
    txt_size = (.8*screensize[0],0)
    color = VIDEO_TEXT_COLOR_DEFAULT
    font = VIDEO_TEXT_FONT_DEFAULT
    font_size = VIDEO_FONT_SIZE_DEFAULT
    list_video_clips = []
    # Criando introdução
    txt_clip1 = TextClip(VIDEO_INTRODUCTION_TEXT,color=color, font=font, kerning = VIDEO_TEXT_KERNING_DEFAULT, fontsize=100, method='caption', size=txt_size).set_pos(VIDEO_TEXT_POSITION_DEFAULT).set_duration(VIDEO_FRAME_DURATION_DEFAULT)
    txt_clip2 = TextClip('um pouco sobre...',color=color, font=font, kerning = VIDEO_TEXT_KERNING_DEFAULT, fontsize=100, method='caption', size=txt_size).set_pos(VIDEO_TEXT_POSITION_DEFAULT).set_duration(VIDEO_FRAME_DURATION_DEFAULT)
    txt_clip3 = TextClip(video_content['search_term'],color=color, font=font, kerning = VIDEO_TEXT_KERNING_DEFAULT, fontsize=100, method='caption', size=txt_size).set_pos(VIDEO_TEXT_POSITION_DEFAULT).set_duration(VIDEO_FRAME_DURATION_DEFAULT)
    img_clip = ImageClip("{}../../default/bg_default_new.jpeg".format(path)).set_duration(VIDEO_FRAME_DURATION_DEFAULT)
    cvc1 = CompositeVideoClip([img_clip,txt_clip1], size=screensize)
    cvc2 = CompositeVideoClip([img_clip,txt_clip2], size=screensize)
    cvc3 = CompositeVideoClip([img_clip,txt_clip3], size=screensize)
    list_video_clips.append(cvc1)
    list_video_clips.append(cvc2)
    list_video_clips.append(cvc3)

    #for root, folders, files in os.walk('./content/images'):
    for idx, sentence in enumerate(video_content['sentences']):

        f  = "{}/{}_composite.jpg".format(path, idx)
        filename = f if os.path.exists(f) else CONTENT_DEFAULT_PATH+"/missing_image.jpg"
        
        # workaround to resolve the problems with grayscale image
        '''
        img = Image.open(filename)
        rgbimg = Image.new(formatter.get(img.format, 'RGB'), img.size)
        rgbimg.paste(img)
        rgbimg.save(filename, format=img.format)
        '''

        duration = int(len(sentence['text'])/15)

        txt_clip = TextClip('{}'.format(sentence['text']),color=color, font=font, kerning = 5, fontsize=font_size, method='caption', size=txt_size).set_pos('center').set_duration(duration)
        im_width, im_height = txt_clip.size
        color_clip = ColorClip(size=(1920, int(im_height*3)), color=(0, 0, 0)).set_pos('center')
        color_clip = color_clip.set_opacity(.6)
        
        clip_to_overlay = CompositeVideoClip([color_clip, txt_clip], size=screensize).set_position('center')
        #cvc = CompositeVideoClip([txt_clip,img_clip], size=screensize)
        if os.path.isfile(filename):
            img_clip = ImageClip(filename).set_duration(duration)
            cvc = CompositeVideoClip([img_clip,clip_to_overlay], size=screensize).set_duration(duration)
        else:
            cvc = CompositeVideoClip([clip_to_overlay], size=screensize).set_duration(duration)

        list_video_clips.append(cvc)
    
    # Criando o fechamento do video
    txt_fechamento = TextClip('Obrigado por assistir!!!\nd(~.~)b',color=color, font=font, kerning = 5, fontsize=100, method='caption', size=screensize).set_pos('center').set_duration(4)
    list_video_clips.append(txt_fechamento)

    #final_clip = concatenate_videoclips(clips)
    #final_clip.write_videofile('./content/coolTextEffects.avi',fps=25,codec='mpeg4')
    final_clip = concatenate_videoclips(list_video_clips)

    audio = afx.audio_loop(music, duration=final_clip.duration)
    final = final_clip.set_audio(audio).audio_fadeout(5)
    final.write_videofile('{}/{}'.format(CONTENT_PATH, video_filename),fps=VIDEO_FPS_DEFAULT,codec=VIDEO_CODEC_DEFAULT)

No início da função já criei alguns dados que serão úteis mais pra frente, e salvei no arquivo content.json.

Depois criei um objeto contendo a música que utilizei de fundo. Para esse projeto, deixei uma música fixa de fundo, a mesma para todos os videos.

Depois setei algumas variáveis contendo as configurações do video, como resolução, tamanho, cor e fonte do texto, tamanho da tarja preto onde posicionei o texto.

Todas as configurações relacionadas ao texto foram para chegar nesse resultado:

Após o ajuste da configuração do video, criei os 3 primeiros slides do video com textos padrões de apresentação e uma imagem fixa.

Após, comecei a iterar nas sentenças para incluir o texto, e buscando a imagem correspondente aquela sentença para incluir de background.

Para conseguir chegar numa duração adequada para cada slide, cronometrei quanto tempo eu levava para ler tranquilamente um texto com 150 caracteres, +- 10 segundos. Então para setar a duração do slide contei o número de caracteres de cada sentença e dividi por 15:

duration = int(len(sentence['text'])/15)

Com isso, cada slide ficou com uma duração adequada de acordo com o tamanho da sentença que será exibida.

Por fim, inclui um texto padrão de encerramento do video, inclui a música padrão, e mandei compilar o video. O trecho que compila o video é esse abaixo, e encontra-se no finalzinho da function:

final.write_videofile('{}/{}'.format(CONTENT_PATH, video_filename),fps=VIDEO_FPS_DEFAULT,codec=VIDEO_CODEC_DEFAULT)

Eis o resultado do video sobre Bitcoin:

https://variavelconstante.com.br/wp-content/uploads/2022/02/Um-pouco-sobre-Bitcoin.mp4

Muito legal né 😀

Agora é só fazer o upload para o Youtube. No próximo post eu mostro como eu fiz!

Abraço!

Video Creator – #Parte 8: Tratamento das imagens

Bom dia, boa tarde, boa noite!

Essa é a oitava parte da série sobre o projeto Video Creator, onde mostrarei como fiz o tratamento das imagens que serão utilizadas nos videos.

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

Aqui começamos a desenvolver o penúltimo robô, o rvideo.py, que será responsável por tratar as imagens e compilar o video.

Nesse post mostrarei o tratamento das imagens com uma ferramenta fantástica chamada ImageMagick.

Existe uma outra ferramenta fantástica que cheguei a testar, mas na hora acabei mantendo o ImageMagick por já estar um pouco mais familiarizado. Mas para quem quiser conhecer, segue o link da lib:

https://pypi.org/project/Pillow/

Requisitos:

Para realizar o tratamento das imagens, precisei do ImageMagick instalado, e de algumas outras libs:

import sys
sys.path.insert(0, './')
from PIL import Image
import os
import rcontent

A instrução sys.path.insert(0, './') server para que eu consiga importar para o código, arquivos de outras pastas.

ImageMagick

Para começar, precisamos instalar o ImageMagick na máquina.

No site do programa você encontra toda a documentação necessária para você fazer o que quiser, além de exemplos e os links para download do programa.

No ubuntu precisei apenas digitar:

sudo apt-get install imagemagick

Tratamento das imagens:

Para o tratamento das imagens, encapsulei todo o código dentro da função prepare_images_downloaded(). Ficou assim:


def prepare_images_downloaded():
    logging.info("Preparing images downloaded to compile the video")
    
    #os.chdir('./')
    #path = "./content/images"
    path = CONTENT_IMAGES_PATH
    # removing existing composite images
    os.system("rm -rf {}/*_composite*".format(path))
    # create directory
    video_content = rcontent.load()
        
    for root, folders, files in os.walk('./content/images'):

        for f in files:
            f_split = f.split("_")
            idx_sentence = int(f_split[0])
            filename_original = os.path.join(root, f)
            filename_composite = "{}/{}_composite.jpg".format(path, f_split[0])
            filename_resized = "{}/{}_resized.jpg".format(path, f_split[0])
            sentence = video_content['sentences'][idx_sentence]['text']
            # creating comands
            create_image_resized = "convert {} -resize '1280x720' {}".format(filename_original, filename_resized)
            create_image_composite = "convert {} -background 'white' -blur '0x9' -resize '1920x1080^' -extent 1920x1080 \( {} -bordercolor white -border 10x10 \) -compose over -gravity center -composite {}".format(filename_original, filename_resized, filename_composite)
            
            # creating images
            os.system(create_image_resized)
            os.system(create_image_composite)

            # removing resized images
            os.system("rm -rf {}/*_resized*".format(path))

            # workaround to resolve the problems with grayscale image
            img = Image.open(filename_composite)
            rgbimg = Image.new(IMAGE_FORMATTER.get(img.format, 'RGB'), img.size)
            rgbimg.paste(img)
            rgbimg.save(filename_composite, format=img.format)

A função é bem simples, mas ela faz muita coisa legal.

Primeira ela elimina da pasta de imagens todas as imagens que contenham _composite no nome.

Depois carrega o conteúdo do arquivo content.json para dentro do objeto video_content.

Depois itera nas imagens da pasta com as imagens baixadas, e converte todas elas para o tamanho de 1280 x 720px.

Após, cria uma montagem esticando a imagem principal para cobrir todo o frame e cria o efeito embaçado. Depois coloca no centro da imagem a foto redimensionada anteriormente.

O resultado é bem legal 😀

Imagem original
Imagem editada

Notem que no final da function, existe um trecho onde precisei utilizar a lib Pillow para tratar um problema de imagens em grayscale na hora de compilar o video.

Bom, agora que temos as imagens tratadas, hora de compilar o video!

Mostro como eu fiz no próximo post!

Abraço.

Video Creator – #Parte 7: Busca e download de imagens

Bom dia, boa tarde, boa noite!

Essa é a sétima parte da série sobre o projeto Video Creator, onde mostrarei como fiz a busca pelas imagens.

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

Basicamente o que fiz foi concatenar o assunto pesquisado com a primeira keyword da lista de keywords de cada sentença.

Bem simples!

Requisitos:

O principal requisito, é claro, é óbvio… criação do Bing Resource na Azure e a criação da instância do Bing Custom Search. Tudo isso eu mostrei no último post!

Utilizei também 2 imports praticamente pradrões nesse projeto:

import json
import requests

rimage.py

A parte da busca e do download das imagens, ficou no robô rimage.py.

A chamada ao endpoint do ping resolvi colocar dentro de uma função que recebe 2 parâmetros: a query de pesquisa e a quantidade de imagens que o serviço irá devolver. Ficou assim:

# implementando busca de imagens pelo Bing
def search_images_on_bing(query, count="1"):
    logging.info("Searching images on Bing with custom bing image search")

    url = credentials['bing_endpoint'] + "?q=" + query + "&count="+ count +"&customconfig=" + credentials['bing_custom_config_id'] +"&licence=ShareCommercially&size=Large"
    r = requests.get(url, headers={'Ocp-Apim-Subscription-Key': credentials['azure_subscription_key']})
    response = json.loads(r.text)
    if 'value' in response:
        result = [d['contentUrl'] for d in response['value']]
    else:
        result = []
    
    return result

Notem que essa função é bem simples, recebe uma query e a quantidade que ela deve retornar de resultado, ai foi só bater na api passando esses parâmetros, fazer o parser do json e extrair somente os links das imagens para enviar como retorno.

Para esse projeto eu utilizei os seguintes parâmetros:

  • licence=ShareCommercially
    • Retornará imagens que podem ser usadas com propósito pessoal ou comercial
  • size=Large
    • Retornará imagens de no mínimo 500×500 pixels

A Api do bing é realmente fantástica. Existem inúmeros parâmetros que podem ser usados para refinar a busca. Quem quiser personalizar ainda mais a busca, só ver os parametros disponíveis na página oficial da api:

https://docs.microsoft.com/en-us/rest/api/cognitiveservices-bingsearch/bing-custom-images-api-v7-reference

Criei uma outra função responsável por iterar nas sentenças, montar a query de pesquisa e chamar a função acima. Ficou assim:


def fetch_images_from_sentences():
    logging.info("Fetching images from sentences...")
    print("Fetching images from sentences", end='\n\n')
    # loading content
    logging.info("Get sentences from object saved")
    print("Loading content from content.json")
    video_content = rcontent.load()
    for sentence in video_content['sentences']:
        if len(sentence['keywords'])>0:
            if video_content['search_term'] != sentence['keywords'][0]:
                sentence['image_search_query'] = "{} {}".format(video_content['search_term'], sentence['keywords'][0])
                sentence['images'] = search_images_on_bing(sentence['image_search_query'], "5")
                time.sleep(1.5)
    rcontent.save(video_content)

Três pontos importantes aqui:

O primeiro é sobre o atributo sentence['image_search_query'], onde eu armazeno o termo que eu utilizei para buscar as imagens, e ele é composto pelo assunto do video + a primeira keyword da lista. Isso para evitar que seja buscado imagens com uma keyword muito genérica que não tenha ligação com o assunto principal.

O segundo é sobre como foi construído o fluxo das coisas. O robô de imagem funciona completamente independente dos demais. O que ele precisa é que exista um arquivo content.json na estrutura esperada. Por isso quando rodamos o rimage.py, ele utiliza a função load() do rcontent.py para buscar o conteúdo do video e efetuar a busca das imagens. Após encontrar as imagens, ele gera uma lista com os links e grava dentro do atributo images, dentro de sentences.

O terceiro ponto é o time.sleep(1.5) no final da função, que precisei incluir para respeitar os limites de Free Tier do Bing Resource. Com isso, eu garanto que só vou fazer 1 chamada por segundo.

Download das imagens

Para o download das imagens, inclui todos os passos dentro de uma função que eu chamei de download_images(). Ficou assim:


def download_images():
    logging.info("Downloading images from each sentences")
    #os.chdir('./')
    path = CONTENT_IMAGES_PATH

    # create directory
    os.makedirs(path, exist_ok=True)
    # limpando a pasta
    os.system("rm -rf {}/*".format(path))
    video_content = rcontent.load()

    list_img = []

    for idx_s, sentence in enumerate(video_content['sentences']):
        for idx_i, image in enumerate(sentence['images']):
            if image not in list_img:
                # if an image doesn't downloaded, try another one
                try:
                    print("Trying to download: ", image)
                    image_filename = "{}/{}_original.jpg".format(path,idx_s)
                    wget.download(image,image_filename)
                    list_img.append(image)
                    print("")
                    break
                except Exception as ex:
                    logging.error(ex)
                    continue
    
    video_content['images_used'] = list_img
    rcontent.save(video_content)

O que essa função faz é basicamente:

  1. verificar se a pasta onde ficará as imagens já existe, se não… cria…
  2. Remover todas as imagens existentes na pasta
  3. Carrega o conteúdo da content.json para buscar os links das imagens buscadas
  4. Itera nas sentenças e nas imagens encontradas.
  5. Faz uma verificação se a imagem já não foi baixada
  6. Tenta realizar o download da imagem, se por algum motivo ele não conseguir, ele tenta a próxima imagem.
  7. Grava o link das imagens baixadas dentro do atributo images_used e salva o objeto em content.json

Next steps?!

Com conteúdo textual e imagens devidamente providenciadas, é hora de começar a montar o video.

Mostro como fiz, no próximo post!

Abraço!

Python na prática: Projeto Video Creator – #Parte 6: Bing Custom Search!

Bom dia, boa tarde, boa noite!

Essa é a sexta parte da série sobre o projeto Video Creator, onde mostrarei um pouco sobre o Bing Custom Search.

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

Aqui resolvi seguir um caminho um pouco diferente do projeto original, que utilizou o Custom Search do Google, e resolvi utilizar o Bing Custom Search para realizar a busca pelas imagens.

Nos testes que realizei, os resultados do Google não foram satisfatórios, trazendo imagens completamente sem ligação com o conteúdo. Não sei o porquê!

Então resolvi pesquisar outras formas de buscar imagens e gostei muito do Bing, tanto na acurácia dos resultados, quando nas opções de filtros disponíveis.

Requisitos:

O caminho não é muito diferente do Google, você precisará de:

1) Criar conta em https://portal.azure.com/
2) Criar uma subscription pay as you go
3) Criar o bing resource com Free Tier
4) Criar uma instancia do bing custom search

Criando a conta na Azure:

Este passo é muito simples, basta entrar nesse endereço: https://portal.azure.com e clicar em criar conta:

layout fev/2022

Os próximos passos, é o mesmo de qualquer serviço web.

Criar uma subscription pay as you go

Ao finalizar a criação da conta e realizar o login, você verá as seguinte tela:

layout fev/2022

Clique em Subscriptions e depois, na opção Add +. Na tela que abrir, escolha Pay as you Go. Você pode criar uma Free trial se quiser. Como o recurso possui um Free tier, se você ficar dentro dos limites, você não será cobrado.

layout fev/2022

Agora vem uma parte um pouco mais chata, que é onde você precisa preencher alguns dados pessoais, e os dados da forma de pagamento.

Reforçando: Para este recurso que utilizei no projeto, vamos usar a camada de Free tier. Se você respeitar os limites dessa camada, você não será cobrado.

layout fev/2022

Criar o Bing Custom Search

Após a criação da subscription, busque por bing resource no topo da pagina, e escolha a opção Bing Resources na seção Services

Na tela que abrir, clique em Add+ e depois em Bing Custom Search.

Na nova tela, dê um nome para o recurso, selecione a subscription, e no Pricing Tier, selecione a opção F0, essa opção é referente ao Free Tier. Lembre-se de respeitar os limites de chamada.

layout fev/2022

Após tudo preenchido, clique em criar. Pronto :D.

Agora entre na página do recurso que você acabou de criar, e procure por Manage keys e clique em click here to manage keys.

Na tela que abrir você terá acesso a 2 chaves, você precisará delas para fazer as chamadas.

layout fev/2022

Eu salvei a Key 1 dentro do atributo azure_subscription_key no meu arquivo credentials.yml.

Criar uma instancia do bing custom search

Acesso a página https://www.customsearch.ai/applications e faça login com sua conta Azure.

Clique em New Instance. Na tela que abrir, digite um nome para a nova instancia.

layout fev/2022

Após a nova instancia criada, clique nela e vc entrará na tela de configuração dessa instancia. No topo da página, clique em Production:

layout fev/2022

Precisaremos apenas guardar a Custom Configuration ID. Criei uma variável dentro do arquivo credentials.yml chamada bing_custom_config_id, onde eu guardei esse ID.

E agora?!

Nesse momento, já temos os recursos necessários para realizar a pesquisa das imagens.

Mas se quiser testar, na página do recurso no Portal Azure, vai ter uma aba chamada Sample code, onde você terá alguns exemplos de código para testar.

Você precisará apenas da subscription key, que é a Key do recurso, o endpoint e o id da instancia do Custom Search que criamos logo acima.

Próximo passo? Buscar as imagens 😀

Abraço

Python na prática: Projeto Video Creator – #Parte 5: Persistência do conteúdo.

Bom dia, boa tarde, boa noite!

Essa é a quinta parte da série sobre o projeto Video Creator, onde mostrarei a persistência do conteúdo textual em disco.

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

Assim como no projeto original, optei por não utilizar nenhuma persistência em banco de dados, e resolvi persistir o conteúdo em um arquivo .json dentro da pasta content para agilizar o processo de desenvolvimento do projeto e diminuir a complexidade, um vez que não preciso prover nenhuma estrutura adicional de persistência.

Com certeza, a parte mais simples e tranquila do projeto! 🙂

Requisitos

Utilizei os seguintes imports:

import json
import o

Para realizar a persistência em disco, criei a função save(). Ela ficou assim:


def save(content):
    logging.info("Saving content...")
    print("Saving content...", end='\n\n')
    # creating name folder
    path = "content"
    # create directory
    os.makedirs(os.path.dirname("{}/content.json".format(path)), exist_ok=True)

    with open("{}/content.json".format(path), "w", encoding='utf8') as f:
        try:
            json.dump(content, f, indent=4, ensure_ascii=False)
        except Exception as ex:
            print(ex)

Bem simples, a função recebe um objeto, verifica se o mesmo já existe, e cria o arquivo.

Com isso já temos todo o nosso conteúdo textual.

No próximo post, vou mostrar como fiz a busca e o download das imagens.

Abraço

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!

Exit mobile version