2019-09-24 00:10:50 +02:00
|
|
|
|
#!/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
|
|
|
|
|
"""
|
2019-10-10 00:39:04 +02:00
|
|
|
|
import argparse
|
2019-10-09 00:35:35 +02:00
|
|
|
|
import datetime
|
2019-09-24 00:10:50 +02:00
|
|
|
|
import json
|
|
|
|
|
import logging
|
2019-10-10 00:20:30 +02:00
|
|
|
|
import re
|
2019-09-23 22:29:13 +02:00
|
|
|
|
import requests
|
2019-09-24 00:10:50 +02:00
|
|
|
|
import textwrap
|
2019-10-09 00:35:35 +02:00
|
|
|
|
import tmdbsimple
|
2019-10-08 23:32:04 +02:00
|
|
|
|
from pyfbx.pyfbx import Fbx
|
2019-09-23 22:29:13 +02:00
|
|
|
|
from bs4 import BeautifulSoup
|
2019-09-28 23:45:49 +02:00
|
|
|
|
from collections import deque
|
2019-09-23 22:29:13 +02:00
|
|
|
|
|
2019-10-05 16:54:08 +02:00
|
|
|
|
|
2019-10-06 00:59:41 +02:00
|
|
|
|
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 = ''
|
2019-10-06 23:08:44 +02:00
|
|
|
|
self.user_selected = False
|
2019-10-10 00:20:30 +02:00
|
|
|
|
self.date = 0
|
|
|
|
|
self.start_time = 0
|
|
|
|
|
self.end_time = 0
|
2019-10-06 00:59:41 +02:00
|
|
|
|
|
|
|
|
|
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):
|
2019-10-09 00:35:35 +02:00
|
|
|
|
return "Movie <{} (D:{} — Ch:{} – R:{})>".format(
|
2019-10-09 00:01:29 +02:00
|
|
|
|
self.title,
|
2019-10-09 00:35:35 +02:00
|
|
|
|
'Today' if self.day == '' else self.day,
|
2019-10-09 00:01:29 +02:00
|
|
|
|
self.channel,
|
|
|
|
|
self.rating
|
|
|
|
|
)
|
2019-10-06 00:59:41 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TVGuideScraper:
|
2019-10-09 00:01:29 +02:00
|
|
|
|
TV_GUIDE_URL = 'https://www.programme-television.org/{}?bouquet=free'
|
2019-10-05 16:54:08 +02:00
|
|
|
|
|
2019-10-06 01:45:40 +02:00
|
|
|
|
def findAllMovies():
|
|
|
|
|
movies = []
|
|
|
|
|
days = deque(['lundi', 'mardi', 'mercredi',
|
|
|
|
|
'jeudi', 'vendredi', 'samedi', 'dimanche'])
|
|
|
|
|
offset = datetime.datetime.today().weekday()
|
|
|
|
|
days.rotate(-1-offset)
|
|
|
|
|
days.appendleft('')
|
2019-10-10 00:20:30 +02:00
|
|
|
|
date = datetime.date.today()
|
2019-10-06 01:45:40 +02:00
|
|
|
|
for day in days:
|
2019-10-10 00:20:30 +02:00
|
|
|
|
movies += TVGuideScraper._getMovies(day, date)
|
|
|
|
|
date += datetime.timedelta(days=1)
|
2019-10-06 01:45:40 +02:00
|
|
|
|
logging.info('Found the following movies: {}'.format(movies))
|
|
|
|
|
return movies
|
|
|
|
|
|
2019-10-06 00:59:41 +02:00
|
|
|
|
@staticmethod
|
2019-10-10 00:20:30 +02:00
|
|
|
|
def _getMovies(day='', date=datetime.date.today()):
|
2019-10-06 00:59:41 +02:00
|
|
|
|
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()
|
2019-10-10 00:20:30 +02:00
|
|
|
|
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
|
2019-10-06 00:59:41 +02:00
|
|
|
|
|
|
|
|
|
logging.info('Found movie: {0!r}'.format(movie))
|
|
|
|
|
movies.append(movie)
|
|
|
|
|
|
|
|
|
|
return movies
|
2019-09-24 00:10:50 +02:00
|
|
|
|
|
2019-09-23 22:29:13 +02:00
|
|
|
|
@staticmethod
|
|
|
|
|
def _tag_is_film(tag):
|
2019-10-06 00:59:41 +02:00
|
|
|
|
"""
|
|
|
|
|
Helper to check if a tag is a film
|
|
|
|
|
"""
|
2019-09-23 22:29:13 +02:00
|
|
|
|
return (
|
|
|
|
|
tag.has_attr('data-nature')
|
2019-10-05 16:54:08 +02:00
|
|
|
|
and
|
|
|
|
|
tag['data-nature'] == 'films-telefilms'
|
2019-09-23 22:29:13 +02:00
|
|
|
|
)
|
2019-10-05 16:54:08 +02:00
|
|
|
|
|
2019-10-10 00:20:30 +02:00
|
|
|
|
@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)
|
|
|
|
|
|
2019-10-06 00:59:41 +02:00
|
|
|
|
|
|
|
|
|
class FreeboxMoviePlanner:
|
2019-10-06 01:45:40 +02:00
|
|
|
|
def __init__(self, movies):
|
2019-10-06 00:59:41 +02:00
|
|
|
|
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']
|
2019-10-06 01:45:40 +02:00
|
|
|
|
self.movies = movies
|
2019-10-06 00:59:41 +02:00
|
|
|
|
|
2019-10-08 23:32:04 +02:00
|
|
|
|
logging.info('Opening Freebox session')
|
|
|
|
|
self.freebox = Fbx()
|
|
|
|
|
self.freebox.mksession(
|
|
|
|
|
app_id='FreeboxMoviePlanner',
|
|
|
|
|
token=self.config['freebox-session-token']
|
|
|
|
|
)
|
|
|
|
|
self.getListOfAvailableChannels()
|
2019-10-09 00:35:35 +02:00
|
|
|
|
self.excludeUnavailableChannels()
|
2019-10-09 00:06:59 +02:00
|
|
|
|
self.findMoviesOnTMDB()
|
2019-10-09 00:35:35 +02:00
|
|
|
|
self.excludeBadRatings()
|
2019-10-10 00:20:30 +02:00
|
|
|
|
self.askForUserSelection()
|
|
|
|
|
self.excludeNotSelected()
|
|
|
|
|
self.programMovies()
|
2019-10-10 01:06:09 +02:00
|
|
|
|
self.checkForConflicts()
|
2019-10-08 23:32:04 +02:00
|
|
|
|
|
2019-10-06 00:59:41 +02:00
|
|
|
|
def __repr__(self):
|
|
|
|
|
result = 'FreeboxMoviePlanner <Movies:\n'
|
|
|
|
|
for movie in self.movies:
|
|
|
|
|
result += ' {!r}\n'.format(movie)
|
|
|
|
|
result += '>'
|
|
|
|
|
return result
|
|
|
|
|
|
2019-10-08 23:32:04 +02:00
|
|
|
|
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))
|
|
|
|
|
|
2019-10-06 00:59:41 +02:00
|
|
|
|
def printAllMovies(self):
|
2019-10-06 23:08:44 +02:00
|
|
|
|
for movie in self.movies:
|
|
|
|
|
print('{!r}'.format(movie))
|
|
|
|
|
|
|
|
|
|
def askForUserSelection(self):
|
2019-10-06 00:59:41 +02:00
|
|
|
|
for movie in self.movies:
|
|
|
|
|
print(movie)
|
2019-10-10 00:39:04 +02:00
|
|
|
|
reply = input("Interested? (y)es/(N)o/(s)kip")
|
2019-10-06 23:08:44 +02:00
|
|
|
|
if reply.upper() == "Y":
|
|
|
|
|
movie.user_selected = True
|
2019-10-10 00:39:04 +02:00
|
|
|
|
elif reply.upper() == "S":
|
|
|
|
|
break
|
2019-10-06 00:59:41 +02:00
|
|
|
|
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):
|
2019-09-24 00:10:50 +02:00
|
|
|
|
logging.info("Searching for '{}' on TMDB".format(movie))
|
|
|
|
|
search = tmdbsimple.Search()
|
|
|
|
|
search.movie(query=movie, language=self.config['tmdb-language'])
|
|
|
|
|
if len(search.results):
|
2019-10-06 00:59:41 +02:00
|
|
|
|
logging.info("Found '{}'".format(
|
|
|
|
|
search.results[0]['title']
|
|
|
|
|
))
|
2019-09-24 00:10:50 +02:00
|
|
|
|
return search.results[0]
|
|
|
|
|
else:
|
2019-10-06 00:59:41 +02:00
|
|
|
|
logging.warning("'{}' not found on TMDB!".format(movie))
|
2019-09-24 00:10:50 +02:00
|
|
|
|
return []
|
2019-09-28 23:45:49 +02:00
|
|
|
|
|
2019-10-09 00:35:35 +02:00
|
|
|
|
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]
|
|
|
|
|
|
2019-10-10 00:20:30 +02:00
|
|
|
|
def programMovies(self):
|
|
|
|
|
for movie in self.movies:
|
2019-10-10 00:39:04 +02:00
|
|
|
|
logging.info("Programming '{}'".format(movie))
|
2019-10-10 00:20:30 +02:00
|
|
|
|
data = {
|
2019-10-10 00:39:04 +02:00
|
|
|
|
'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']
|
2019-10-10 00:20:30 +02:00
|
|
|
|
}
|
|
|
|
|
self.freebox.Pvr.Create_a_precord(data)
|
2019-10-10 00:39:04 +02:00
|
|
|
|
print("Programmed '{}'".format(movie))
|
2019-10-10 00:20:30 +02:00
|
|
|
|
|
2019-10-10 01:06:09 +02:00
|
|
|
|
def checkForConflicts(self):
|
|
|
|
|
programmed_movies = self.freebox.Pvr.Getting_the_list_of_precords()
|
|
|
|
|
conflicting_movies = [m for m in programmed_movies if m['conflict']]
|
|
|
|
|
if conflicting_movies:
|
|
|
|
|
print(
|
|
|
|
|
"!!!!!!!!!\n"
|
|
|
|
|
"!Warning!\n"
|
|
|
|
|
"!!!!!!!!!\n"
|
|
|
|
|
"Conflicting records detected, please "
|
|
|
|
|
"check your Freebox interface"
|
|
|
|
|
)
|
|
|
|
|
logging.info("Conflicting records detected '{}'".format(
|
|
|
|
|
conflicting_movies
|
|
|
|
|
))
|
|
|
|
|
|
2019-10-05 16:54:08 +02:00
|
|
|
|
|
2019-09-23 22:29:13 +02:00
|
|
|
|
if __name__ == '__main__':
|
2019-09-24 00:10:50 +02:00
|
|
|
|
logging.basicConfig(
|
2019-10-10 01:06:09 +02:00
|
|
|
|
level=logging.WARNING,
|
2019-09-24 00:10:50 +02:00
|
|
|
|
format=' %(asctime)s - %(levelname)s - %(message)s'
|
|
|
|
|
)
|
2019-10-10 01:06:09 +02:00
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
description='Schedule movie recordings on your Freebox'
|
|
|
|
|
)
|
2019-10-10 00:39:04 +02:00
|
|
|
|
parser.add_argument(
|
|
|
|
|
'-d', '--day',
|
|
|
|
|
action='store_true',
|
2019-10-10 01:06:09 +02:00
|
|
|
|
help='Search movies for current day only instead of a full week'
|
2019-10-10 00:39:04 +02:00
|
|
|
|
)
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
if args.day:
|
|
|
|
|
movies = TVGuideScraper._getMovies()
|
|
|
|
|
else:
|
|
|
|
|
movies = TVGuideScraper.findAllMovies()
|
|
|
|
|
fmp = FreeboxMoviePlanner(movies)
|