Sugestão aleatória de filmes e séries para assistir por streaming
Publicado por Pedro Fernandes (última atualização em 30/04/2023)
[ Hits: 1.742 ]
Homepage: https://github.com/PedroF37
Projeto Python para sugerir aleatoriamente filmes/séries para assistir em streaming, usando a API (não oficial) do Justwatch.
Aplicativo sugere filme/série para assistir, de todos os serviços de streaming, todos os gêneros e todas as classificações to IMDb, ou, dos streamings, gêneros e classificações especificadas.
Aplicativo sugere baseado nesses parâmetros aleatoriamente e tenta garantir que não sugere títulos repetidos, salvando os títulos sugeridos em arquivo e verificando se o título escolhido se encontra ou não no arquivo.
Se quiser adicionar mais serviços de streaming, adiciona dentro do script, no dicionário "PROVIDERS_DICT". É algo como: Disney-Plus: dnp (dnp é o nome que aparece na API do justwatch e na própria URL do justwatch).
Se na documentação da API não mostrar o streaming em questão, basta entrar no site do justwatch e selecionar o streaming, que irá aparecer as siglas na URL.
Original:
https://github.com/PedroF37/Streaming
# -------------------------------------------------------------------------- # # IMPORTAÇÕES # tkinter from tkinter import Tk, Frame, Label, Button from tkinter import PhotoImage, Listbox, Variable from tkinter.messagebox import showerror, showinfo from tkinter.ttk import Style, Combobox # JustWatch from justwatch import JustWatch # random from random import choice # requests from requests import get from requests.exceptions import HTTPError, ConnectionError # os from os import remove # pillow from PIL import Image, ImageTk # webbrowser from webbrowser import open as op # atexit from atexit import register # sys from sys import exit # -------------------------------------------------------------------------- # # CONSTANTES # Cores COLOR1 = '#292929' # Fundo COLOR2 = '#c7c5c5' # Janela COLOR3 = '#fffbef' # Letra # Caminho do poster para ser deletado no final POSTER_PATH = './poster.jpg' # Arquivo com as sugestões dadas SUGESTIONS_FILE = './sugetions.txt' # URLs BASE_URL = 'https://www.justwatch.com' BASE_IMAGE_URL = 'https://images.justwatch.com' # Dicionário mapeia os streamings para os códigos # deles na url do JustWatch PROVIDERS_DICT = { 'Disney-Plus': 'dnp', 'GloboPlay': 'gop', 'HBO-MAX': 'hbm', 'Netflix': 'nfx', 'Prime-Video': 'prv', 'Star-Plus': 'srp' } # Mapeia série e filme (português) para o que vai na url CONTENT_TYPES_DICT = {'Série': 'show', 'Filme': 'movie'} # Mapeia os géneros GENRES_DICT = { 'Ação & Aventura': 'act', 'Comédia': 'cmy', 'Documentário': 'doc', 'Fantasia': 'fnt', 'Terror': 'hrr', 'Música & Musical': 'msc', 'Romance': 'rma', 'Esportes & Fitness': 'spt', 'Western': 'wsn', 'Animação': 'ani', 'Crime': 'crm', 'Drama': 'drm', 'História': 'hst', 'Família': 'fml', 'Mistério & Thriller': 'trl', 'Ficção Científica': 'scf', 'Guerra & Militar': 'war', 'Reality TV': 'rly' } # Streamings, filme/série e géneros # para os Combobox e Listbox stream_list = [key for key in PROVIDERS_DICT] content_type_list = [key for key in CONTENT_TYPES_DICT] genre_list = [key for key in GENRES_DICT] [ item.insert(0, 'TODOS') for item in (stream_list, content_type_list, genre_list) ] # Lista de Rating do imdb rating_list = [ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 ] rating_list.insert(0, 'QUALQUER') global poster_img, button_img global thumbs_up_img, cool_img, imdb_img # -------------------------------------------------------------------------- # # FUNÇÕES def remove_poster(): """Cuida de remover o poster baixado.""" try: remove(POSTER_PATH) except OSError: pass def delete_sugestions(): """Cuida de remover arquivo com sugestões passadas.""" try: remove(SUGESTIONS_FILE) showinfo('Sucesso', 'Sugestões deletadas com sucesso') except OSError: showinfo('', 'Não possui sugestões gravadas ainda') def mount_query(streaming_listbox, genres_select, items): """Cuida de montar a requisição.""" # Aqui, destroi para não acontecer de os labels ficarem colados # ao apertar botão novamente. [widget.destroy() for widget in output_frame.winfo_children()] # Acho que não é necessario, o poster é sobsecrito aqui # mas acho que não faz mal também kkk! remove_poster() # Cria listas de streammings escolhidos e géneros escolhidos. streamings_list = [ streaming_listbox.get(i) for i in streaming_listbox.curselection() ] genres_list = [ genres_select.get(i) for i in genres_listbox.curselection() ] if 'TODOS' in streamings_list or not streamings_list: # Lista streaming = list(PROVIDERS_DICT.values()) else: # Itens enviados como lista streaming = [PROVIDERS_DICT[i] for i in streamings_list] if 'TODOS' in genres_list or not genres_list: # Lista genres = list(GENRES_DICT.values()) else: # Itens enviados como lista genres = [GENRES_DICT[i] for i in genres_list] if items[0] == 'TODOS' or items[0] == '': # Lista types = list(CONTENT_TYPES_DICT.values()) else: # Item enviado como lista types = [CONTENT_TYPES_DICT[items[0]]] # Aqui, pega de qualquer rating, ou do # rating especificado até ao rating de 10.0 if items[1] == 'QUALQUER' or items[1] == '': rating = { "imdb:score": { "min_scoring_value": 0.0, "max_scoring_value": 10.0 } } else: rating = { "imdb:score": { "min_scoring_value": float(items[1]), "max_scoring_value": 10.0 } } query(streaming, types, genres, rating) def query(streaming, types, genres, rating): """Cuida de fazer a consulta""" try: just_watch = JustWatch(country='BR') results = just_watch.search_for_item( providers=streaming, content_types=types, genres=genres, scoring_filter_types=rating, page_size=100 ) except ConnectionError: showerror('', 'Verifique sua conexão e tente mais tarde') exit() except HTTPError: showerror('', 'Erro inesperado na requisição. Tente mais tarde') exit() else: parse_results(results) def parse_results(results): """Cuida de pegar os dados.""" ''' Aqui, dependendo da combinação (género, filme/serie etc), pode não achar algum item. Exemplo, se colocar: GloboPlay, Documentário, Série e classificação a partir de 6.0, dá erro de KeyError, porque (neste caso) não tem poster. Mas quem garante que o globoplay tem documentários? kk.. Então, para não usar um try/except para cada item, fiz assim kk. ''' try: # Pega os títulos titles = [item['title'] for item in results['items']] # Pega os links para o filme ou seŕie links = [ f"{BASE_URL}{item['full_path']}" for item in results['items'] ] # Pega o link para o poster do tamanho especificado. # Aqui tamanho é s332 posters = [ f"{BASE_IMAGE_URL}{item['poster'].replace('{profile}', 's332')}" for item in results['items'] ] ''' Aqui, quero pegar o rating do imdb. Mas o json é muito complicado kk. Tem dicionário dentro de lista com lista dentro de dicionário. Para piorar, o dicionário onde está o rating do imdb é {'provider_type': 'imdb:score', 'value': 8.5}, e este dicionário está em posições diferentes para cada lista kk. E não posso pegar só pelo provider_type, pois tem outros dicionários com provider_type cujos valores não são imdb:score. Então, score_list, pega a lista de listas com os dicionários. Depois imdb_list, pega da lista de listas o rating (na chave value), se o valor da chave provider_type for imdb:score ''' scorelist = [ results['items'][score]['scoring'] for score in range(len(results['items'])) ] imdb_scores = list() for item in range(len(scorelist)): for provider in scorelist[item]: if provider['provider_type'] == 'imdb:score': imdb_scores.append(provider['value']) except KeyError: showinfo( '', 'Não foram emcontrados itens com a combinação' \ ' de parâmetros especificados' ) return else: # Dicionario que mapeia os titulos ao link. # Ex: 'Tulsa King': 'https://....serie/tulsa-king' title_link_dict = { key: value for (key, value) in zip(titles, links) } # Dicionario que mapeia titulos para o link do poster title_poster_dict = { key: value for (key, value) in zip(titles, posters) } # Dicionário que mapeia títulos ao rating do imdb title_score_dict = { key: value for (key, value) in zip(titles, imdb_scores) } pick_sugestion( title_link_dict, title_poster_dict, title_score_dict ) def was_sugested(title): """ Cuida de varrer o arquivo para vêr se título já foi sugerido. """ ''' Retornos 1 - Arquivo existe e título já foi seugerido. 2 - Arquivo existe e título ainda não foi sugerido. 3 - Arquivo não existe ainda. Será criado e título será escrito. ''' try: with open(SUGESTIONS_FILE, 'r', encoding='utf-8') as f: found = False for line in f: if title in line: found = True if found: return 1 else: return 2 except IOError: # Aqui, arquivo estará vazio ainda. with open(SUGESTIONS_FILE, 'w', encoding='utf-8') as f: f.write(f'{title}\n') return 3 def pick_sugestion( title_link_dict, title_poster_dict, title_score_dict ): """Escolhe o título para sugerir""" title, link = choice(list(title_link_dict.items())) exists = was_sugested(title) count = 0 while exists == 1: count += 1 if count == 5: showinfo( '', 'Estamos com problemas em sugerir algo novo.' \ ' Experimente deletar sugeridos, ou alterar os parâmetros' \ ' para a sugestão. Iremos terminar o programa.' ) exit() title, link = choice(list(title_link_dict.items())) exists = was_sugested(title) if exists == 2: # Arquivo existe, mas título não foi sugerido ainda. # Será escrito aqui no arquivo. with open(SUGESTIONS_FILE, 'a', encoding='utf-8') as f: f.write(f'{title}\n') poster = get(title_poster_dict[title]) ''' Aqui, conto com que se não deu erro na função query, não será aqui meros segundos depois que a conexão falhará. Por isso não uso aqui try/except. Mas concerteza que pode acontecer né? kkk!! ''' with open(POSTER_PATH, 'wb') as f: [f.write(chunk) for chunk in poster] show_output(title, link, title_poster_dict, title_score_dict) def show_output(title, link, title_link_dict, title_score_dict): """Mostra o resultado.""" global poster_img, button_img global thumbs_up_img, cool_img, imdb_img thumbs_up_img = Image.open('icones/thumbs-up.png') thumbs_up_img = thumbs_up_img.resize((25, 25)) thumbs_up_img = ImageTk.PhotoImage(thumbs_up_img) label = Label( output_frame, text='Vamos Assistir:....', font=('Roboto 10 bold'), anchor='center', bg=COLOR1, fg=COLOR3 ) label.place(x=150, y=0) title_label = Label( output_frame, text=f' {title}?', font=('Roboto 10 bold'), anchor='nw', image=thumbs_up_img, bg=COLOR1, fg=COLOR3, compound='left' ) title_label.place(x=80, y=25) imdb_img = Image.open('icones/imdb.png') imdb_img = imdb_img.resize((15, 15)) imdb_img = ImageTk.PhotoImage(imdb_img) imdb_rating_label = Label( output_frame, image=imdb_img, compound='left', text=f' Classificação: {title_score_dict[title]:.1f}', font=('Roboto 8 bold'), anchor='nw', bg=COLOR1, fg=COLOR3 ) imdb_rating_label.place(x=150, y=55) poster_img = Image.open(POSTER_PATH) poster_img = ImageTk.PhotoImage(poster_img) poster_img_label = Label( output_frame, text='', image=poster_img ) poster_img_label.place(x=50, y=80) cool_img = Image.open('icones/cool.png') cool_img = cool_img.resize((25, 25)) cool_img = ImageTk.PhotoImage(cool_img) link_label = Label( output_frame, text=' Veja mais informações! ', font=('Roboto 10 bold'), fg=COLOR3, bg=COLOR1, image=cool_img, compound='left' ) link_label.place(x=120, y=560) button_img = Image.open('icones/button.png') button_img = button_img.resize((45, 45)) button_img = ImageTk.PhotoImage(button_img) link_label_button = Label( output_frame, text='', image=button_img, cursor='hand2', bg=COLOR1 ) link_label_button.place(x=180, y=610) link_label_button.bind( '<Button-1>', lambda open_link: callback(link) ) def callback(url): """Cuida de abrir o link""" op(url) # -------------------------------------------------------------------------- # # JANELA window = Tk() window.title('') window.geometry('1000x720') window.resizable(0, 0) window.configure(bg=COLOR2) style = Style(window) style.theme_use('clam') # -------------------------------------------------------------------------- # # FRAMES title_frame = Frame( window, width=1000, height=48, bg=COLOR1 ) title_frame.grid( row=0, column=0, sticky='nsew' ) input_frame = Frame( window, width=448, height=670, bg=COLOR1 ) input_frame.place(x=0, y=50) output_frame = Frame( window, width=552, height=670, bg=COLOR1 ) output_frame.place(x=450, y=50) # -------------------------------------------------------------------------- # # CONFIGURANDO TITLE_FRAME glasses_img = PhotoImage(file='icones/glasses.png') sad_img = PhotoImage(file='icones/sad.png') title_label = Label( title_frame, text=' Assistir o quê?? SOCORRO!!!', image=glasses_img, compound='left', font=('Roboto 20 bold'), anchor='nw', bg=COLOR1, fg=COLOR3 ) title_label.place(x=10, y=10) sad_label = Label( title_frame, text='', image=sad_img, compound='right', bg=COLOR1 ) sad_label.place(x=420, y=10) # -------------------------------------------------------------------------- # # CONFIGURANDO INPUT_FRAME # Streamings streamings_label = Label( input_frame, text='Streaming', font=('Roboto 12 bold'), anchor='nw', bg=COLOR1, fg=COLOR3 ) streamings_label.place(x=30, y=20) stream = Variable(value=stream_list) streaming_listbox = Listbox( input_frame, font=('Roboto 10'), listvariable=stream, selectmode='multiple', exportselection=0 ) streaming_listbox.place(x=30, y=60) # Tipos (filme/serie) types_label = Label( input_frame, text='Filme/Série', font=('Roboto 12 bold'), anchor='nw', bg=COLOR1, fg=COLOR3 ) types_label.place(x=30, y=270) types_combobox = Combobox( input_frame, width=17, font=('Roboto 10'), values=content_type_list, state='readonly' ) types_combobox.place(x=30, y=310) # Género (ação/terror etc) genres_label = Label( input_frame, text='Género', font=('Roboto 12 bold'), anchor='nw', bg=COLOR1, fg=COLOR3 ) genres_label.place(x=200, y=20) genre = Variable(value=genre_list) genres_listbox = Listbox( input_frame, font=('Roboto 10'), listvariable=genre, selectmode='multiple', exportselection=0 ) genres_listbox.place(x=200, y=60) # Rating Imdb rating_label = Label( input_frame, text='Rating (A partir de)', font=('Roboto 12 bold'), anchor='nw', bg=COLOR1, fg=COLOR3 ) rating_label.place(x=200, y=270) rating_combobox = Combobox( input_frame, width=17, font=('Roboto 10'), values=rating_list, state='readonly' ) rating_combobox.place(x=200, y=310) # Botão para pegar o item find_img = PhotoImage(file='icones/find.png') find_button = Button( input_frame, text='SUGERIR', font=('Roboto 8 bold'), relief='ridge', overrelief='sunken', bg=COLOR1, fg=COLOR3, activebackground=COLOR1, activeforeground=COLOR3, image=find_img, cursor='hand2', compound='left', anchor='nw', command=lambda: mount_query( streaming_listbox, genres_listbox, [types_combobox.get(), rating_combobox.get()] ) ) find_button.place(x=145, y=400) # Botão para deletar arquivo # com sugestões passadas trash_img = Image.open('icones/trash.png') trash_img = trash_img.resize((15, 15)) trash_img = ImageTk.PhotoImage(trash_img) delete_sugested_button = Button( input_frame, text=' DELETAR SUGERIDOS', font=('Roboto 8 bold'), relief='ridge', overrelief='sunken', bg=COLOR1, fg=COLOR3, activebackground=COLOR1, activeforeground=COLOR3, image=trash_img, compound='left', anchor='nw', command=delete_sugestions ) delete_sugested_button.place(x=110, y=450) # -------------------------------------------------------------------------- # # LOOP # Remove o poster baixado quando sair do app register(remove_poster) window.mainloop() # -------------------------------------------------------------------------- #
Calculadora para números complexos
Just Do It - XML Generic Editor
Gerador de números para Mega-Sena
Enviar mensagem ao usuário trabalhando com as opções do php.ini
Meu Fork do Plugin de Integração do CVS para o KDevelop
Compartilhando a tela do Computador no Celular via Deskreen
Como Configurar um Túnel SSH Reverso para Acessar Sua Máquina Local a Partir de uma Máquina Remota
Configuração para desligamento automatizado de Computadores em um Ambiente Comercial
Compartilhamento de Rede com samba em modo Público/Anônimo de forma simples, rápido e fácil
Cups: Mapear/listar todas as impressoras de outro Servidor CUPS de forma rápida e fácil
Criando uma VPC na AWS via CLI
Tem como instalar o gerenciador AMD Adrenalin no Ubuntu 24.04? (16)
Arch Linux - Guia para Iniciantes (2)
Problemas ao instalar o PHP (11)
Tenho dois Link's ( IP VÁLIDOS ), estou tentando fazer o failover... (0)