FreeboxMoviePlanner/FreeboxMoviePlanner.py

283 lines
9.7 KiB
Python
Raw Normal View History

#!/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
"""
import argparse
2019-10-09 00:35:35 +02:00
import datetime
import json
import logging
2019-10-10 00:20:30 +02:00
import re
import requests
import textwrap
2019-10-09 00:35:35 +02:00
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 = ''
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
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
)
class TVGuideScraper:
2019-10-09 00:01:29 +02:00
TV_GUIDE_URL = 'https://www.programme-television.org/{}?bouquet=free'
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
@staticmethod
2019-10-10 00:20:30 +02:00
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()
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
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'
)
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)
class FreeboxMoviePlanner:
2019-10-06 01:45:40 +02:00
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']
2019-10-06 01:45:40 +02:00
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()
2019-10-09 00:35:35 +02:00
self.excludeUnavailableChannels()
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()
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):
2019-10-06 23:08:44 +02:00
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")
2019-10-06 23:08:44 +02:00
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 []
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:
logging.info("Programming '{}'".format(movie))
2019-10-10 00:20:30 +02:00
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']
2019-10-10 00:20:30 +02:00
}
self.freebox.Pvr.Create_a_precord(data)
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
))
if __name__ == '__main__':
logging.basicConfig(
2019-10-10 01:06:09 +02:00
level=logging.WARNING,
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'
)
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'
)
args = parser.parse_args()
if args.day:
movies = TVGuideScraper._getMovies()
else:
movies = TVGuideScraper.findAllMovies()
fmp = FreeboxMoviePlanner(movies)