#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fileencoding=utf-8
#
# class Film.py
#
# Copyright 2019 Robert Sebille <robert@sebille.name>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
#
from time import sleep
from os import listdir
[docs]class Film:
"""Cette classe fournit des outils pour afficher des petits films
à partir de frames en ascii.
Les frames peuvent être dans un seul fichier, avec un séparateur ou
dans plusieurs fichiers qui seront lus d'un répertoire à préciser,
et dans l'ordre alphanumérique croissant.
Pour démarrer, essayez ceci, ci dessous ?
.. code-block::
from film.film import Film
Film.demo()
.. seealso::
Dépot: https:///framagit.org/zenjo/film/tree/master
"""
_version = "0.0.18"
def __init__(self, titre="", dict_sous_titres={}):
"""
Constructeur
self._ecran: bascule pour l'effacement de l'écran entre les
méthodes projection et dessine_ecran
"""
self._titre = str(titre)
self._frames = []
self._reverse = False
self._affiche_titre = True
self._affiche_ctrl_c = False
self._ctrl_c_xy = (0, 15)
self._titre_x = 4
self._titre_xy = (self.titre_x, 2)
self._dict_sous_titres = dict_sous_titres
self._sous_titre_xy = (20, 15)
self._sous_titres_largeur = 40
self._sous_titres_reverse = False
self._deplacement = 1
self._affiche_no_frame = False
self._no_frame_xy = (0, 0)
self._nb_frames = 0
self._nb_frames_per_file = 0
self._ecran = False
def __str__(self):
"""Retoune la chaîne "Film : Titre du film" """
return str("Film : " + self.titre)
# Private
def __ccls(cls): # clear the screen
return "\033[2J"
__ccls = classmethod(__ccls)
def __ccxy2(self, x, y): # position cursor at x across, y down
return "\033["+str(y)+";"+str(x)+"f"
def __ccxy1(self, coords): # position cursor at coords[0] across, ...
print("\033["+str(coords[1])+";"+str(coords[0])+"f", sep="", end="")
def __csrv(self): # print reverse
return "\033[7m"
def __csrs(self): # print, reset all
return "\033[0m"
# Public
# class methods
[docs] def version(cls):
"""Retourne la version de la classe"""
return cls._version
version = classmethod(version)
[docs] def delay(cls, secondes):
"""Evite l'importation de la méthode sleep du module time"""
sleep(secondes)
delay = classmethod(delay)
[docs] def clearsc(cls):
"""Efface l'écran."""
print(cls.__ccls())
clearsc = classmethod(clearsc)
[docs] def demo(cls):
"""Lance une demo amusante de la classe"""
from film.demo import demo
demo()
demo = classmethod(demo)
# property
def get_titre(self):
return self._titre
def set_titre(self, titre):
self._titre = str(titre)
titre = property(get_titre, set_titre, '',
"""
Retourne, modifie ou crée le titre du film. Défaut = ''""")
def set_frame(self, fichier, sep="#"):
self._nb_frames_per_file = 0
with open(fichier) as c:
liste = c.readlines()
s = "".join(liste)
liste = s.split(sep)
for l in liste:
self._frames.append(l.split("\n"))
self._nb_frames_per_file += 1
self._nb_frames += 1
def del_frame(self):
self._frames = []
self._affiche_no_frame = False
self._nb_frames = 0
self._nb_frames_per_file = 0
frame = property('', set_frame, del_frame,
"""
Lit ou ajoute des frames à partir d'un seul fichier. Ces frames sont
séparées par un séparateur, par défaut #
del frame détruit toutes les frames du film
""")
def set_frames(self, repertoire):
frms = []
for fichier in listdir(repertoire):
with (open(repertoire + '/' + fichier)) as f:
frms.append(f.read())
self._nb_frames += 1
for f in frms:
self._frames.append(f.split("\n"))
self._nb_frames_per_file = 1
def del_frames(self):
self.del_frame()
frames = property('', set_frames, del_frames,
"""
Lit ou ajoute des frames à partir de fichiers dans un répertoire.
Chaque frame est dans un fichier propre. Les fichiers sont lus dans
le répertoire dans le sens alphnumérique croissant de leurs noms.")
del frames détruit toutes les frames du film
""")
def get_nb_frames(self):
return self._nb_frames
nb_frames = property(get_nb_frames, '', '',
"""
Retourne
- 0 si aucune frame n'est définie
- le nombre de frames du film
.. note::
sera egal à nb_frames_per_file dans le cas d'un fichier unique
""")
def get_nb_frames_per_file(self):
return self._nb_frames_per_file
nb_frames_per_file = property(get_nb_frames_per_file, '', '',
"""
Retourne
- 0 si aucune frame n'est définie
- 1 si les dernières frames chargées le sont à partir d'1 fichier
par frame (cas de frames)
- le nombre de frames dans le dernier fichier chargé si il s'agit
d'un fichier unique
""")
def get_titre_x(self):
return self._titre_x
def set_titre_x(self, titre_x):
self._titre_x = titre_x
self._titre_xy[0] = titre_x
titre_x = property(get_titre_x, set_titre_x, '',
"""
Retourne ou fixe la coordonnée x d'affichage du titre. Defaut=(4)
Déprécié: utilisez titre_xy = (x, y) à la place
.. note::
Si vous utilisez titre_x, la valeur x de titre_xy sera automatiquement
adaptée
""")
def get_titre_xy(self):
return self._titre_xy
def set_titre_xy(self, titre_xy):
self._titre_xy = titre_xy
self._titre_x = titre_xy[0]
titre_xy = property(get_titre_xy, set_titre_xy, '',
"""
Retourne ou fixe les coordonnées x, y d'affichage du titre.
Defaut=(4,2)
obj.titre_xy = (x, y) # tuple
.. note::
obj.titre_x sera adapté à la valeur x de obj.titre_xy
""")
def get_dict_sous_titres(self):
return self._dict_sous_titres
def set_dict_sous_titres(self, dict_sous_titres):
self._dict_sous_titres = dict_sous_titres
dict_sous_titres = property(get_dict_sous_titres, set_dict_sous_titres, '',
"""
Retourne, modifie ou crée le dictionnaire des sous-titres
Ce dictionnaire est de la forme: {(a, b): "sous-titre"}
ou a = n° de la frame où l'affichage de "sous-titre" démarre et
b = n° - 1 de la frame où s'arrête l'affichage de "sous-titre"
""")
def get_sous_titre_xy(self):
return self._sous_titre_xy
def set_sous_titre_xy(self, sous_titre_x_offset_y):
self._sous_titre_xy = sous_titre_x_offset_y
sous_titre_xy = property(get_sous_titre_xy, set_sous_titre_xy, '',
"""
Retourne ou fixe les coordonnées (x, y) de la zone de sous-titres
Defaut=(20,15)
obj.sous_titre_xy = (x, y) # tuple
La zone de sous-titres débute à la coordonnée x de la largeur
fixée (par défaut 40) par l'attribut sous_titres_largeur. Les sous-titres
sont centrés dans cette zone.
.. note::
Pour mieux voir et comprendre ceci, mettez le paramètre
sous_titres_reverse à True, le temps éventuellement de tester
la projection de votre film.
.. code-block::
largeur zone sous-titre: -> +-------- 20 --------+
sous_titre_xy -> | Sous-titre |
le sous-titre est centré dans la zone de sous-titres
""")
def get_sous_titres_largeur(self):
return self._sous_titres_largeur
def set_sous_titres_largeur(self, sous_titres_largeur):
self._sous_titres_largeur = sous_titres_largeur
sous_titres_largeur = property(get_sous_titres_largeur,
set_sous_titres_largeur, '',
"""
Retourne ou fixe la largeur de la zone des sous-titres. Défaut = 40""")
def get_sous_titres_reverse(self):
return self._sous_titres_reverse
def set_sous_titres_reverse(self, sous_titres_reverse):
self._sous_titres_reverse = sous_titres_reverse
sous_titres_reverse = property(get_sous_titres_reverse,
set_sous_titres_reverse, '',
"""
Retourne ou fixe l'affichage des sous-titres à reverse ou non.
(booléen) Défaut = False""")
def get_deplacement(self):
return self._deplacement
def set_deplacement(self, deplacement):
self._deplacement = deplacement
deplacement = property(get_deplacement, set_deplacement, '',
"""
Retourne ou fixe la valeur du déplacement entre 2 frames successives
Défaut = 1""")
def get_reverse(self):
return self._reverse
def set_reverse(self, direction):
self._reverse = direction
reverse = property(get_reverse, set_reverse, '',
"""
Retourne ou fixe le sens du film en avant ou en arrière (booléen)
Défaut = False""")
def get_affiche_titre(self):
return self._affiche_titre
def set_affiche_titre(self, affiche_titre):
self._affiche_titre = affiche_titre
affiche_titre = property(get_affiche_titre, set_affiche_titre, '',
"""
Retourne ou décide si il faut afficher le titre (booléen)
Défaut = True""")
def get_affiche_ctrl_c(self):
return self._affiche_ctrl_c
def set_affiche_ctrl_c(self, affiche_ctrl_c):
self._affiche_ctrl_c = affiche_ctrl_c
affiche_ctrl_c = property(get_affiche_ctrl_c, set_affiche_ctrl_c, '',
"""
Retourne ou décide si il faut afficher une mention CTRL + C (booléen)
Défaut = False""")
def get_ctrl_c_xy(self):
return self._ctrl_c_xy
def set_ctrl_c_xy(self, ctrl_c_xy):
self._ctrl_c_xy = ctrl_c_xy
ctrl_c_xy = property(get_ctrl_c_xy, set_ctrl_c_xy, '',
"""
Retourne ou fixe les coordonnées de l'affichage du CTRL + C, si
affiche_ctrl_c = True. Défaut = (0, 15)""")
def get_affiche_no_frame(self):
return self._affiche_no_frame
def set_affiche_no_frame(self, affiche_no_frame):
self._affiche_no_frame = affiche_no_frame
affiche_no_frame = property(get_affiche_no_frame, set_affiche_no_frame, '',
"""
Retourne ou décide si il faut afficher le n° de la frame en cours (booléen)
Défaut = False
.. note::
Pratique en conjonction avec un long délai dans la méthode projection,
pour régler finement la projection d'un ou de film(s), de sous-titres,
...
Exemple:
.. code-block::
obj.affiche_no_frame = True
obj.projection(delai = 0.5)
""")
def get_no_frame_xy(self):
return self._no_frame_xy
def set_no_frame_xy(self, no_frame_xy):
self._no_frame_xy = no_frame_xy
no_frame_xy = property(get_no_frame_xy, set_no_frame_xy, '',
"""
Retourne ou fixe les coordonnées de l'affichage n° de frame en cours, si
affiche_no_frame = True. Défaut = (0, 0)""")
# public methods
[docs] def projection(self, nb_frames2run=0, x=2, y=4, trace=0, delai=0.1):
"""Lance l'instance film en lisant les frames.
- nb_frames2run: nombre de frames à exécuter. 0 = infini
- x, y: les coordonnées haut gauche de départ du film
- trace nombre de blancs à ajouter à la frame suivant le sens du film.
A gauche si self.reverse = False, à droite si self.reverse = True.
Ce paramètre sert à effacer d'éventuelles traces laissées par une
frame pas trop bien :) formatée.
- delai: la durée en seconde de chaque frame
.. note:: Le nombre de frames réellement exécutées sera le premier
multiple de nb_frames au dessus de nb_frames2run - 1. nb_frames
est égal au nombre de frames du film. Ainsi, pour un nb_frames2run
= 17 et un nb_frames = 4, le nombre de frames exécutées sera 20 - 1
= 20 (puisque le numérotage démarre à 0). Utilisez
affiche_no_frame = True pour mieux visualiser cela.
.. warning:: Si self.reverse = True, le film peut avoir des effets de
bord non désiré quand x < 0. Vous pouvez régler cela via
l'appel de la méthode.
"""
if not self._ecran:
self.clearsc()
else:
self._ecran = False
if self.affiche_titre:
self.__ccxy1(self.titre_xy)
print(self.get_titre())
if self.reverse:
after = " " * trace
before = ""
else:
after = ""
before = " " * trace
no_frame = 0
while no_frame < nb_frames2run or nb_frames2run == 0:
try:
for f in self._frames:
# affichage du n° de la frame
if self.affiche_no_frame and self.nb_frames != 0:
self.__ccxy1(self.no_frame_xy)
print("frame " + str(no_frame % self.nb_frames) + " "
+ str(no_frame) + " " + str(nb_frames2run))
# affichage de la frame
for l in range(len(f)):
self.goto(x, y + l)
print(before + f[l] + after)
# Traitement des sous-titres
self.__ccxy1(self._sous_titre_xy)
print(" "*40)
for k, v in self._dict_sous_titres.items():
if no_frame in range(int(k[0]), int(k[1])):
self.__ccxy1(self._sous_titre_xy)
if self.sous_titres_reverse:
self.print_reverse(
v.center(self.
sous_titres_largeur, " "))
else:
print(v.center(self.sous_titres_largeur, " "))
# affichage éventuel du CTRL+C
if self.affiche_ctrl_c:
self.__ccxy1(self.ctrl_c_xy)
print(" CTRL + C to stop.")
sleep(delai)
if self.reverse:
x -= self.deplacement
else:
x += self.deplacement
# delta no_frame
no_frame += 1
except KeyboardInterrupt:
print("\nReçu CTRL + C")
print("Bye !")
exit(0)
[docs] def goto(self, x, y):
"""Amène le curseur en x, y pour impression (print)"""
print(self.__ccxy2(x, y), sep="", end="")
[docs] def print_reverse(self, message):
"""Inverse couleur et background pour l'impression d'un message"""
print(self.__csrv() + message + self.__csrs())
[docs] def print_message(self, x, y, message, delai=2, rev=True):
"""
Affiche un message en reverse à x,y pour delai seconde (2 par défaut)
"""
self.goto(x, y)
self.print_reverse(" " + message + " ") if rev else print(message)
self.delay(delai)
self.goto(x, y)
print(" "*(len(message)+2))
[docs] def dessine_ecran(self, x0, y0, x1, y1, g="j", h="+", d="l", b="=",
c1="\\", c2="/"):
"""
Dessine un écran, coin haut gauche = x0, y0, bas droit = x1, y1.
g = bord gauche, h = haut, d = droit, b = bas, c1 = coin 1, c2 =coin 2
.. code-block::
(x0,y0) -> c1hhhhhhc2 <- (x1,y0) obj.dessine-ecran(1, 1, 10, 5)
g d retournera \\++++++++/
g d par défaut j l
g d j l
g d j l
(x0,y1) -> c2bbbbbbc1 <- (x1,y1) /========\\
"""
self.clearsc()
self._ecran = True
self.goto(x0, y0)
print(c1, sep="", end="")
print(h*(x1-x0-1), sep="", end="")
print(c2)
for i in range((y0+1), (y1)):
self.goto(x0, i)
print(g, sep="", end="")
self.goto(x1, i)
print(d)
self.goto(x0, y1)
print(c2, sep="", end="")
print(b*(x1-x0-1), sep="", end="")
print(c1)