19 Commits

  1. 97
      FreeboxMoviePlanner.py
  2. 32
      Readme.md

97
FreeboxMoviePlanner.py

@ -7,6 +7,7 @@ import argparse
import datetime
import json
import logging
import os
import re
import requests
import socket
@ -33,15 +34,17 @@ class Movie:
self.date = 0
self.start_time = 0
self.end_time = 0
self.year = ''
def __str__(self):
return '{}: {} - {} ({})\n TMDB: {} - {}\n @ {}\n {}'.format(
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.year,
self.url,
self.overview
)
@ -74,14 +77,15 @@ class TVGuideScraper:
@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))
url = TVGuideScraper.TV_GUIDE_URL.format(day)
logging.info('Connecting to {}'.format(url))
r = requests.get(url)
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):
for movietag in channel.find_all(TVGuideScraper._tag_is_movie):
movie = Movie()
movie.title = \
movietag.select('.texte_titre a')[0]['title']
@ -109,9 +113,9 @@ class TVGuideScraper:
return movies
@staticmethod
def _tag_is_film(tag):
def _tag_is_movie(tag):
"""
Helper to check if a tag is a film
Helper to check if a tag is a movie
"""
return (
tag.has_attr('data-nature')
@ -132,7 +136,7 @@ class TVGuideScraper:
class FreeboxMoviePlanner:
def __init__(self, movies):
def __init__(self, movies, excluded_channels=[], excluded_directory=[]):
logging.debug('Opening config file: config.json')
with open('config.json') as config_file:
self.config = json.load(config_file)
@ -140,18 +144,21 @@ class FreeboxMoviePlanner:
self.createAuthenticationToken()
tmdbsimple.API_KEY = self.config['tmdb-api']
self.movies = movies
self.excluded_directory = excluded_directory
logging.info('Opening Freebox session')
self.freebox = Fbx(nomdns=True)
self.freebox = Fbx()
self.freebox.mksession(
app_id='FreeboxMoviePlanner',
token=self.config['freebox-session-token']
)
self.getListOfAvailableChannels()
self.getListOfAvailableChannels(excluded_channels)
self.excludeUnavailableChannels()
self.excludeTelevisionFilm()
self.excludeTelevisionMovie()
self.findMoviesOnTMDB()
self.excludeBadRatings()
for directory in self.excluded_directory:
self.excludeLocalMovies(directory)
self.askForUserSelection()
self.excludeNotSelected()
self.programMovies()
@ -159,7 +166,7 @@ class FreeboxMoviePlanner:
def createAuthenticationToken(self):
logging.info('Creating authentication token')
self.freebox = Fbx(nomdns=True)
self.freebox = Fbx()
hostname = socket.gethostname()
print("You don't seem to have an authentication token.")
print("I will now atempt to create one.")
@ -178,12 +185,17 @@ class FreeboxMoviePlanner:
result += '>'
return result
def getListOfAvailableChannels(self):
def getListOfAvailableChannels(self, excluded_channels):
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']
if channel['name'] in excluded_channels:
logging.debug(
"Excluding '{}'".format(channel['name'])
)
else:
self.channels[channel['name']] = channel['uuid']
else:
logging.debug("Dropping '{}'".format(channel['name']))
logging.debug('Got the following channels: {}'.format(self.channels))
@ -217,6 +229,19 @@ class FreeboxMoviePlanner:
float(movie.rating) >= self.config['minimum-rating']
movie.url = 'https://www.themoviedb.org/movie/{}?language={}' \
.format(movie.tmdb_id, self.config['tmdb-language'])
try:
movie.year = datetime.datetime.strptime(
tmdb_details['release_date'], '%Y-%m-%d'
).year
except (ValueError, KeyError):
logging.warning(
"No release date for '{!r}'".format(movie)
)
pass
else:
logging.warning(
"'{!r}' not found on TMDB!".format(movie)
)
def _findMovieOnTMDB(self, movie):
logging.info("Searching for '{}' on TMDB".format(movie))
@ -228,7 +253,7 @@ class FreeboxMoviePlanner:
))
return search.results[0]
else:
logging.warning("'{}' not found on TMDB!".format(movie))
logging.info("'{}' not found!".format(movie))
return []
def excludeBadRatings(self):
@ -245,12 +270,21 @@ class FreeboxMoviePlanner:
self.movies = [m for m in self.movies if m.channel in self.channels]
logging.debug('Kept {}'.format(self.movies))
def excludeTelevisionFilm(self):
logging.info('Dropping television films')
def excludeTelevisionMovie(self):
logging.info('Dropping television movies')
self.movies = [
m for m in self.movies if not m.genre.startswith("Téléfilm")
]
def excludeLocalMovies(self, directory):
(_, _, filenames) = next(os.walk(directory))
logging.warning('Dropping movies already recorded: {}'.format(
[m for m in self.movies if m.title+'.m2ts' in filenames]
))
self.movies = [
m for m in self.movies if m.title+'.m2ts' not in filenames
]
def excludeNotSelected(self):
self.movies = [m for m in self.movies if m.user_selected]
@ -278,7 +312,8 @@ class FreeboxMoviePlanner:
"!Warning!\n"
"!!!!!!!!!\n"
"Conflicting records detected, please "
"check your Freebox interface"
"check your Freebox interface\n"
"http://192.168.1.254/#Fbx.os.app.pvr.app"
)
logging.info("Conflicting records detected '{}'".format(
conflicting_movies
@ -298,11 +333,37 @@ if __name__ == '__main__':
action='store_true',
help='Search movies for current day only instead of a full week'
)
parser.add_argument(
'-l', '--log',
action='store_true',
help='Display more log messages'
)
parser.add_argument(
'-e', '--exclude',
action='append',
default=[],
help='Exclude the following Channel'
)
parser.add_argument(
'-x', '--exclude-directory',
action='append',
default=[],
help='''Do not display movies available in the following directory.
This will prevent you from recording the same movie multiple
times.'''
)
args = parser.parse_args()
print("Working the magic, please wait…")
if args.log:
logging.getLogger().setLevel(logging.INFO)
if args.day:
movies = TVGuideScraper._getMovies()
else:
movies = TVGuideScraper.findAllMovies()
fmp = FreeboxMoviePlanner(movies)
fmp = FreeboxMoviePlanner(
movies,
excluded_channels=args.exclude,
excluded_directory=args.exclude_directory
)

32
Readme.md

@ -25,10 +25,11 @@ Concrètement, l'outil effectue les opérations suivantes :
Installation
---
### À partir de l'exécutable
[Téléchargez](https://djib.fr/djib/FreeboxMoviePlanner/releases) le fichier qui correspond à votre système d'exploitation (Linux ou Windows) dans le répertoire `dist` ainsi que le fichier `config.template.json`.
[Téléchargez](https://djib.fr/djib/FreeboxMoviePlanner/releases) le fichier qui correspond à votre système d'exploitation (Linux ou Windows).
Extrayez ensuite ce fichier à l'emplacement de votre choix.
### À partir des sources
Pour utiliser **FreeboxMoviePlanner** à partir des sources, il vous faut Python, ainsi que les 3 packages suivants (à installer par exemple avec `pip install [nom du packet]` :
Pour utiliser **FreeboxMoviePlanner** à partir des sources, il vous faut Python, ainsi que les 3 packages suivants (à installer par exemple avec `pip install [nom du packet]`) :
* `beautifulsoup4`
* `tmdbsimple`
@ -43,7 +44,7 @@ Configuration
---
Ensuite, il vous faut créer un fichier de conf `config.json`. Vous pouvez tout simplement renommer le fichier `config.template.json`.
Dans ce fichier vous devrez renseigner une clef API TMDb. Pour cela vous aurez besoin d'ouvrir un compte sur leur site et de [demander une clef API](https://www.themoviedb.org/settings/api).
Dans ce fichier vous devrez renseigner une clef API TMDb. Pour cela vous aurez besoin d'[ouvrir un compte](https://www.themoviedb.org/account/signup) sur leur site et de [demander une clef API](https://www.themoviedb.org/settings/api).
Le fichier de config vous permet aussi de configurer la marge avant et après chaque enregistrement, ou la note en dessous de laquelle les films ne s'afficheront pas.
@ -51,5 +52,26 @@ Exécution
---
L'outil devrait vous afficher rapidement le petit texte `Working the magic, please wait…` puis, après environ une minute (ou plus si votre réseau est lent), le détail des films.
À la première exécution, l'outil vous demandera aussi d'autoriser le script sur votre Freebox (*Please go to your Freebox and accept the authentication.*)
À ce moment, l'affichage de votre boîtier *Freebox* vous demandera alors de valider cette demande d'authentification. Vous devez donc physiquement valider l'accès à partir de votre box (sur la V6, il s'agit de presser la flèche de droite).
À la première exécution, l'outil vous demandera aussi d'autoriser le script sur votre Freebox (`Please go to your Freebox and accept the authentication.`)
À ce moment, l'affichage de votre boîtier *Freebox* vous demandera de valider cette demande d'authentification. Vous devez donc physiquement valider l'accès à partir de votre box (sur la V6, il s'agit de presser la flèche de droite).
Notez que les films de *Canal+* s'afficheront par défaut. La *Freebox* indique que la chaîne est disponible, probablement car elle est parfois visible en clair. Vous pouvez forcer son exclusion avec le paramètre `-e Canal+`.
Enjoy!
Documentation
---
```shell
usage: FreeboxMoviePlanner.py [-h] [-d] [-l] [-e EXCLUDE] [-x EXCLUDE_DIRECTORY]
Schedule movie recordings on your Freebox
optional arguments:
-h, --help show this help message and exit
-d, --day Search movies for current day only instead of a full week
-l, --log Display more log messages
-e EXCLUDE, --exclude EXCLUDE
Exclude the following Channel
-x EXCLUDE_DIRECTORY, --exclude-directory EXCLUDE_DIRECTORY
Do not display movies available in the following directory. This will prevent you from recording the same movie multiple times.
```
Loading…
Cancel
Save