A Python script to help you select the movies you want to record with your Freebox
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

269 lines
9.2 KiB

#!/usr/bin/python3
"""
Simple script that extracts information from Télé 7 jours and TMDB
to help choosing the movies you want to record with your Freebox
Todo :
* Add a configuration setting for beginning and end margin
3 years ago
* Display conflicts if any
"""
import argparse
import datetime
import json
import logging
import re
import requests
import textwrap
import tmdbsimple
from pyfbx.pyfbx import Fbx
from bs4 import BeautifulSoup
from collections import deque
class Movie:
def __init__(self):
self.day = ''
self.title = ''
self.genre = ''
self.channel = ''
self.rating = ''
self.original_title = ''
self.overview = ''
self.good = False
self.tmdb_id = ''
self.url = ''
self.user_selected = False
self.date = 0
self.start_time = 0
self.end_time = 0
def __str__(self):
return '{}: {} - {} ({})\n TMDB: {} - {}\n @ {}\n {}'.format(
'Today' if self.day == '' else self.day,
self.title,
self.genre,
self.channel,
self.rating,
self.original_title,
self.url,
self.overview
)
def __repr__(self):
return "Movie <{} (D:{} — Ch:{} – R:{})>".format(
self.title,
'Today' if self.day == '' else self.day,
self.channel,
self.rating
)
class TVGuideScraper:
TV_GUIDE_URL = 'https://www.programme-television.org/{}?bouquet=free'
def findAllMovies():
movies = []
days = deque(['lundi', 'mardi', 'mercredi',
'jeudi', 'vendredi', 'samedi', 'dimanche'])
offset = datetime.datetime.today().weekday()
days.rotate(-1-offset)
days.appendleft('')
date = datetime.date.today()
for day in days:
movies += TVGuideScraper._getMovies(day, date)
date += datetime.timedelta(days=1)
logging.info('Found the following movies: {}'.format(movies))
return movies
@staticmethod
def _getMovies(day='', date=datetime.date.today()):
logging.info('Connecting to {}'.format(TVGuideScraper.TV_GUIDE_URL))
r = requests.get(TVGuideScraper.TV_GUIDE_URL.format(day))
r.raise_for_status()
html = BeautifulSoup(r.text, 'html.parser')
movies = []
for channel in html.select('.bloc_cnt'):
if len(channel.select('em')):
for movietag in channel.find_all(TVGuideScraper._tag_is_film):
movie = Movie()
movie.title = \
movietag.select('.texte_titre a')[0]['title']
movie.genre = movietag.select('.texte_cat a')[0].string
movie.channel = channel.select('em')[0]\
.string.replace('Programme ', '')
movie.day = day.title()
movie.date = datetime.date.strftime(date, '%Y-%m-%d')
movie.start_time = datetime.datetime.strptime(
'{} {}'.format(
movie.date,
movietag.select('.horaire')[0].string
),
'%Y-%m-%d %H:%M'
)
duration = TVGuideScraper._parse_duration(
movietag.select('.texte_cat')[0]
.contents[1].strip(' \n\t()')
)
movie.end_time = movie.start_time + duration
logging.info('Found movie: {0!r}'.format(movie))
movies.append(movie)
return movies
@staticmethod
def _tag_is_film(tag):
"""
Helper to check if a tag is a film
"""
return (
tag.has_attr('data-nature')
and
tag['data-nature'] == 'films-telefilms'
)
@staticmethod
def _parse_duration(text):
match = re.match(r"((?P<hours>\d+)h)?(?P<minutes>\d+)mn", text)
if not match:
error = "Could not parse duration '{}'".format(text)
logging.error(error)
raise ValueError(error)
hours = int(match.group('hours')) if match.group('hours') else 0
minutes = int(match.group('minutes'))
return datetime.timedelta(hours=hours, minutes=minutes)
class FreeboxMoviePlanner:
def __init__(self, movies):
logging.info('Opening config file: config.json')
with open('config.json') as config_file:
self.config = json.load(config_file)
tmdbsimple.API_KEY = self.config['tmdb-api']
self.movies = movies
logging.info('Opening Freebox session')
self.freebox = Fbx()
self.freebox.mksession(
app_id='FreeboxMoviePlanner',
token=self.config['freebox-session-token']
)
self.getListOfAvailableChannels()
self.excludeUnavailableChannels()
self.findMoviesOnTMDB()
self.excludeBadRatings()
self.askForUserSelection()
self.excludeNotSelected()
self.programMovies()
def __repr__(self):
result = 'FreeboxMoviePlanner <Movies:\n'
for movie in self.movies:
result += ' {!r}\n'.format(movie)
result += '>'
return result
def getListOfAvailableChannels(self):
logging.info('Getting the list of available channels')
self.channels = {}
for channel in self.freebox.Tv.Getting_the_list_of_channels().values():
if channel['available']:
self.channels[channel['name']] = channel['uuid']
else:
logging.info("Dropping '{}'".format(channel['name']))
logging.info('Got the following channels: {}'.format(self.channels))
def printAllMovies(self):
for movie in self.movies:
print('{!r}'.format(movie))
def askForUserSelection(self):
for movie in self.movies:
print(movie)
reply = input("Interested? (y)es/(N)o/(s)kip")
if reply.upper() == "Y":
movie.user_selected = True
elif reply.upper() == "S":
break
print()
def findMoviesOnTMDB(self):
for movie in self.movies:
tmdb_details = self._findMovieOnTMDB(movie.title)
if tmdb_details:
movie.rating = tmdb_details['vote_average']
movie.original_title = \
tmdb_details['original_title']
movie.overview = '\n '.join(textwrap.wrap(
tmdb_details['overview'], 75)
)
movie.tmdb_id = tmdb_details['id']
movie.good = \
float(movie.rating) >= self.config['minimum-rating']
movie.url = 'https://www.themoviedb.org/movie/{}?language={}' \
.format(movie.tmdb_id, self.config['tmdb-language'])
def _findMovieOnTMDB(self, movie):
logging.info("Searching for '{}' on TMDB".format(movie))
search = tmdbsimple.Search()
search.movie(query=movie, language=self.config['tmdb-language'])
if len(search.results):
logging.info("Found '{}'".format(
search.results[0]['title']
))
return search.results[0]
else:
logging.warning("'{}' not found on TMDB!".format(movie))
return []
def excludeBadRatings(self):
logging.info('Dropping novies with bad ratings: {}'.format(
[m for m in self.movies if not m.good]
))
self.movies = [m for m in self.movies if m.good]
logging.info('Kept {}'.format(self.movies))
def excludeUnavailableChannels(self):
logging.info('Dropping movies on unavailable channels: {}'.format(
[m for m in self.movies if m.channel not in self.channels]
))
self.movies = [m for m in self.movies if m.channel in self.channels]
logging.info('Kept {}'.format(self.movies))
def excludeNotSelected(self):
self.movies = [m for m in self.movies if m.user_selected]
def programMovies(self):
for movie in self.movies:
logging.info("Programming '{}'".format(movie))
data = {
'channel_uuid': self.channels[movie.channel],
'start': int(movie.start_time.timestamp()),
'end': int(movie.end_time.timestamp()),
'name': movie.title,
'margin_before': self.config['margin-before'],
'margin_after': self.config['margin-after']
}
self.freebox.Pvr.Create_a_precord(data)
print("Programmed '{}'".format(movie))
if __name__ == '__main__':
logging.basicConfig(
level=logging.INFO,
format=' %(asctime)s - %(levelname)s - %(message)s'
)
parser = argparse.ArgumentParser(description='Plan movies on your Freebox')
parser.add_argument(
'-d', '--day',
action='store_true',
help='Search for a single day ahead'
)
args = parser.parse_args()
if args.day:
movies = TVGuideScraper._getMovies()
else:
movies = TVGuideScraper.findAllMovies()
fmp = FreeboxMoviePlanner(movies)