4thisDir = os.path.dirname(__file__)
5sys.path.insert(0, thisDir)
9from globdef
import HOME_PATH, VIDEO_PATH, CONF_PATH, \
10 ICON_PATH, LANG_PATH, \
11 DATA_PATH, HELP_PATH, DOCUMENT_PATH, \
13from PyQt6.QtWidgets
import QApplication, QMainWindow, QWidget, QLayout, QFileDialog, QTableWidgetItem, QInputDialog, QLineEdit, QMessageBox, QVBoxLayout, QTableWidgetSelectionRange, QDialog, QPushButton
14from PyQt6.QtGui
import QKeySequence, QIcon, QPixmap, QImage, QShortcut, QScreen, QAction
15from PyQt6.QtCore
import QThread, pyqtSignal, QLocale, QTranslator, Qt, QSize, QTimer
27from export
import Export, EXPORT_FORMATS
28from toQimage
import toQImage
29from version
import version, Version, str2version
31from preferences
import Preferences
32from trajWidget
import trajWidget
34from vecteur
import vecteur
35from echelle
import echelle
37import interfaces.icon_rc
41 pymecavideo version %s:
43 a program to track moving points in a video frameset
45 Copyright (C) 2007-2016 Jean-Baptiste Butet <ashashiwa
@gmail.com>
47 Copyright (C) 2007-2023 Georges Khaznadar <georgesk
@debian.org>
49 This program
is free software: you can redistribute it
and/
or modify
50 it under the terms of the GNU General Public License
as published by
51 the Free Software Foundation, either version 3 of the License,
or
52 (at your option) any later version.
54 This program
is distributed
in the hope that it will be useful,
55 but WITHOUT ANY WARRANTY; without even the implied warranty of
56 MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the
57 GNU General Public License
for more details.
59 You should have received a copy of the GNU General Public License
60 along
with this program. If
not, see <http://www.gnu.org/licenses/>.
64 pymecavideo version %s :
66 un programme pour tracer les trajectoires des points dans une vidéo.
68 Copyright (C) 2007-2016 Jean-Baptiste Butet <ashashiwa
@gmail.com>
70 Copyright (C) 2007-2023 Georges Khaznadar <georgesk
@debian.org>
72 Ce projet est un logiciel libre : vous pouvez le redistribuer, le modifier selon les terme de la GPL (GNU Public License) dans les termes de la Free Software Foundation concernant la version 3 ou plus de la dite licence.
74 Ce programme est fait avec l
'espoir qu'il sera utile mais SANS AUCUNE GARANTIE. Lisez la licence pour plus de détails.
76 <http://www.gnu.org/licenses/>.
79# Le module de gestion des erreurs n'est chargé que si on execute le fichier .exe ou si on est sous Linux
81# if sys.platform == "win32" or sys.argv[0].endswith(".exe"):
83thisDir = os.path.dirname(os.path.abspath(__file__))
84sys.path.insert(0, thisDir)
86from inspect import currentframe
90 return cf.f_back.f_lineno
94app = QApplication(sys.argv)
100from interfaces.Ui_pymecavideo
import Ui_pymecavideo
101from etatsMain
import Etats
104 def __init__(self, parent=None, opts=[], args=[]):
106 le constructeur reçoit les données principales du logiciel :
107 @param parent le widget parent,
None pour une fenêtre principale
108 @param opts les options de l
'invocation bien rangées en tableau
109 @param args les arguments restants après raitement des options
111 QMainWindow.__init__(self, parent)
112 Ui_pymecavideo.__init__(self)
125 g = QApplication.instance().screens()[0].geometry()
126 self.height_screen, self.
width_screen = g.height(), g.width()
132 if (
'-d' in o[0])
or (
'--debug' in o[0]):
141 self.
coord.setApp(self)
142 self.
graph.setApp(self)
156 for key
in sorted(EXPORT_FORMATS.keys()):
157 action = QAction(EXPORT_FORMATS[key]
159 action.triggered.connect(
160 lambda checked, index=key: self.
coord.export(index))
177 traite les arguments donnés à l'application. Trouve le type de fichier
178 et selon le cas, ouvre ou "rouvre" son contenu
179 @return vrai si on a réussi à traiter un argument
182 if len(self.
args) > 0:
183 filename = self.
args[0]
184 mime = magic.Magic(mime=
True)
185 mt = mime.from_file(filename)
186 if mt.startswith(
"video/"):
189 elif mt ==
"text/plain":
190 signature = open(filename).read(24)
191 if signature.startswith(
"# version = pymecavideo"):
197 lambda: QMessageBox.information(
199 self.tr(
"Argument non pris en compte"),
200 self.tr(
"Le fichier {filename} n'est ni un fichier vidéo, ni un fichier de sauvegarde de pymecavideo.").format(filename=filename)))
206 Vérifie la version du fichier de préférences
207 @return 0 si la version esty trop ancienne, 1 si elle est voisine,
210 m = re.match(r"pymecavideo (.*)",
211 self.
prefs.config[
"DEFAULT"][
"version"])
213 thisversion = str2version(m.group(1))
219 lambda: QMessageBox.information(
221 self.tr(
"Configuration trop ancienne"),
222 self.tr(
"La version du fichier de configuration, {version} est inférieure à {min_version} : le fichier de configuration ne peut pas être pris en compte").format(version = thisversion, min_version = self.
min_version)))
224 elif thisversion < Version:
227 lambda: QMessageBox.information(
229 self.tr(
"Configuration ancienne"),
230 self.tr(
"La version du fichier de configuration, {version} est inférieure à {Version} : certaines dimensions peuvent être légèrement fausses.").format(version = thisversion, Version = Version)))
238 Récupère les préférences sauvegardées, et en applique les données
239 ici on s'occupe de ce qui se gère facilement au niveau de la
241 @param rouvre est vrai quand on ouvre un fichier pymecavideo ;
242 il est faux par défaut
244 self.dbg.p(2, "rentre dans 'FenetrePrincipale.apply_preferences'")
251 "DEFAULT",
"taille_image")
255 d = self.
prefs.config[
"DEFAULT"]
260 def hasHeightForWidth(self):
265 return QSize(1024, 768)
268 """gère les dimensions en fonction de la largeur et la hauteur de l'écran"""
269 self.setFixedSize(QSize(self.
width_screen,self.height_screen ))
273 """Basculer en mode plein écran / mode fenétré"""
274 self.
dbg.p(2,
"rentre dans 'basculer_plein_ecran'")
276 self.showFullScreen()
281 def init_variables(self, filename=""):
282 self.
dbg.p(2,
"rentre dans 'init_variables'")
288 self.
couleurs = [
"red",
"blue",
"cyan",
"magenta",
"yellow",
"gray",
296 self.
stdout_file = os.path.join(CONF_PATH,
"stdout")
306 selection_done = pyqtSignal()
307 redimensionneSignal = pyqtSignal(bool)
308 updateProgressBar = pyqtSignal()
309 change_etat = pyqtSignal(str)
310 show_coord = pyqtSignal()
311 show_video = pyqtSignal()
312 adjust4image = pyqtSignal()
313 hide_imgdim = pyqtSignal()
314 affiche_statut = pyqtSignal(str)
315 stopRedimensionnement = pyqtSignal()
316 OKRedimensionnement = pyqtSignal()
317 image_n = pyqtSignal(int)
318 new_echelle = pyqtSignal()
321 """connecte les signaux de Qt"""
322 self.
dbg.p(2,
"rentre dans 'connecte_ui'")
334 self.
coord.presse_papier)
357 Cache le widget d'affichage de la dimension de l'image
358 quand son temps de présentation est révolu
368 ajuste progressivement la taille de la fenêtre principale
369 jusqu'à ce que l'image soit à la taille voulue, c
'est à dire
373 w1, h1 = self.width(), self.height()
378 fabrique une fonction qui modifie la taille de la fenêtre
379 principale de (x,y), et relance la recherched e taille idéale
382 self.resize(w1 + x, h1 + y)
389 w, h = int(self.
pointage.video.size().width()), int(self.
pointage.video.size().height())
393 deltaw, deltah = w0 - w, h0 - h
399 w2 = int(abs(deltaw)/deltaw*math.ceil(abs(deltaw/2)))
401 h2 = int(abs(deltah)/deltah*math.ceil(abs(deltah/2)))
407 QTimer.singleShot(delai, modifie(w2, h2))
412 if self.
pointage.etat
not in (
"debut",
"A"):
419 harmonise l'origine : recopie celle de la vidéo vers le
420 widget des trajectoires et redessine les deux.
428 self.qmsgboxencode.updateProgressBar()
430 def _dir(lequel=None, install=None):
431 """renvoie les répertoires utiles.
432 paramètre lequel (chaîne) : peut prendre les valeurs utiles suivantes,
433 "videos",
"home",
"conf",
"images",
"icones",
"langues",
"data",
"help"
434 quand le paramètre est absent, initialise les répertoires si nécessaire
438 elif lequel ==
"videos":
440 elif lequel ==
"conf":
442 elif lequel ==
"icones":
444 elif lequel ==
"langues":
446 elif lequel ==
"data":
448 elif lequel ==
"help":
450 elif type(lequel) == type(
""):
452 1,
"erreur, appel de _dir() avec le paramètre inconnu %s" % lequel)
456 dd = FenetrePrincipale._dir(
"conf")
457 if not os.path.exists(dd)
and \
458 os.access(os.path.dirname(dd), os.W_OK):
460 _dir = staticmethod(_dir)
463 self.
dbg.p(2,
"rentre dans 'rouvre_ui'")
466 fichier, _ = QFileDialog.getOpenFileName(
468 self.tr(
"Ouvrir un projet Pymecavideo"),
470 self.tr(
"Projet Pymecavideo (*.mecavideo)"))
475 def redimensionneFenetre(self, tourne=False):
476 self.
dbg.p(2,
"rentre dans 'redimensionneFenetre'")
478 self.
dbg.p(2,
"Dans 'redimensionneFenetre', tourne")
485 def resizeEvent(self, event):
486 self.
dbg.p(2,
"rentre dans 'resizeEvent'")
496 return super(FenetrePrincipale, self).resizeEvent(event)
498 def showEvent(self, event):
499 self.
dbg.p(2,
"rentre dans 'showEvent'")
504 traite les signaux émis par le changement d'onglet, ou
505 par le changement de référentiel dans l'onglet des trajectoires.
506 @param newIndex la variable transmise par le signal currentChanged
509 self.dbg.p(2, f"rentre dans 'choix_onglets', self.tabWidget.currentIndex() = {self.tabWidget.currentIndex()}, newIndex = {newIndex}")
510 self.statusBar().clearMessage()
519 self.
coord.affiche_tableau()
522 self.
graph.affiche_grapheur()
525 def recommence_echelle(self):
526 self.
dbg.p(2,
"rentre dans 'recommence_echelle'")
535 Un crochet pour y mettre toutes les procédures à faire lors
536 de la fermeture de l'application.
538 self.dbg.p(2, "rentre dans 'closeEvent'")
539 d = self.
prefs.config[
"DEFAULT"]
540 d[
"taille_image"] = f
"({self.pointage.video.image_w},{self.pointage.video.image_h})"
541 d[
"rotation"] = str(self.
pointage.video.rotation)
545 def openexample(self):
546 self.
dbg.p(2,
"rentre dans 'openexample'")
547 dir_ =
"%s" % (self.
_dir_dir(
"videos"))
548 filename, hints = QFileDialog.getOpenFileName(
550 self.tr(
"Ouvrir une vidéo"), dir_,
551 self.tr(
"fichiers vidéos (*.avi *.mp4 *.ogv *.mpg *.mpeg *.ogg *.mov *.wmv)"))
557 Ouvre un dialogue pour choisir un fichier vidéo puis le charge
559 self.dbg.p(2, "rentre dans 'openfile'")
561 filename, hints = QFileDialog.getOpenFileName(
563 self.tr(
"Ouvrir une vidéo"),
565 self.tr(
"fichiers vidéos ( *.avi *.mp4 *.ogv *.mpg *.mpeg *.ogg *.wmv *.mov)"))
567 self.
pointage.reinitialise_capture()
570 def renomme_le_fichier(self):
571 self.
dbg.p(2,
"rentre dans 'renomme_le_fichier'")
572 renomme_fichier = QMessageBox.warning(
574 self.tr(
"Nom de fichier non conforme"),
576Le nom de votre fichier contient des caractères accentués ou des espaces.
577Merci de bien vouloir le renommer avant de continuer"""))
578 filename = QFileDialog.getOpenFileName(
580 self.tr("Ouvrir une vidéo"),
588 self.
dbg.p(2,
"rentre dans 'propos'")
590 loc = locale.getdefaultlocale()[0][0:2]
591 except TypeError
as err:
592 self.
dbg.p(3, f
"***Exception*** {err} at line {get_linenumber()}")
594 if loc
in licence.keys():
595 licence_XX = licence[loc] % Version
597 licence_XX = licence[
"en"] % Version
598 QMessageBox.information(
605 self.
dbg.p(2,
"rentre dans 'aide'")
606 lang = locale.getdefaultlocale()[0][0:2]
607 helpfile =
"%s/help-%s.html" % (self.
_dir_dir(
"help"), lang)
608 if os.path.exists(helpfile):
609 command =
"firefox --new-window %s" % helpfile
610 status = subprocess.call(command, shell=
True)
612 command =
"x-www-browser %s" % helpfile
613 status = subprocess.call(command, shell=
True)
618 self.tr(
"Désolé pas de fichier d'aide pour le langage {0}.").format(lang))
621 def rouvre(self, fichier):
623 Rouvre un fichier pymecavideo précédemment enregistré
625 self.dbg.p(2, "rentre dans 'rouvre'")
627 lignes = open(fichier).readlines()
629 lignes_config = [l
for l
in lignes
if re.match(
"# .* = .*", l)]
630 lignes_config = [
"[DEFAULT]\n"] + [re.sub(
"^# ",
"", l) \
631 for l
in lignes_config]
632 self.
prefs.config.read_string(
"".join(lignes_config))
637 lignes_data = [l
for l
in lignes
if l[0] !=
"#" and len(l.strip()) > 0]
642 data = [re.split(
r"\s+", l.strip().replace(
",",
"."))
643 for l
in lignes_data][1:]
645 data, self.
prefs.config[
"DEFAULT"].getint(
"index_depart"))
652 Fait en sorte que self.pointage.horizontalSlider et self.pointage.spinBox_image
654 @param i le nouvel index
656 self.pointage.spinBox_image.setValue(i)
657 self.pointage.horizontalSlider.setValue(i)
662 Met l'onglet des coordonnées sur le dessus
669 Met l'onglet des vidéos sur le dessus
676 Précise la ligne de statut, qui commence par une indication de l'état
677 @param text un texte pour terminer la ligne de statut
680 self.statusBar().showMessage(self.
roleEtat[self.
etatetat](self) +
"| " + text)
683 def fixeLesDimensions(self):
684 self.setMinimumWidth(self.width())
685 self.setMaximumWidth(self.width())
686 self.setMaximumHeight(self.height())
687 self.setFixedSize(QSize(self.width(), self.height()))
692 donne une taille minimale à la fenêtre, 640 x 480 ;
693 il s'y ajoute bien sûr les contraintes des widgets définis
694 par l'interface utilisateur qui est créée à l'aide de designer.
696 self.setMinimumWidth(0)
697 self.setMaximumWidth(16000000)
698 self.setMinimumHeight(0)
699 self.setMaximumHeight(16000000)
704Usage : pymecavideo [-d (1-3)| --debug=(1-3)] [video | mecavideo]
705 lance pymecavideo, une application d'analyse des mouvements
706 option facultative : -d ou --debug= débogage +- verbeux (entre 1 et 3)
707 argument facultatif : fichier video standard ou fichier mecavideo
709 les fichiers mecavideo sont créés par l'application pymecavideo""")
717 opts, args = getopt.getopt(args,
"d:", [
"debug="])
718 except getopt.GetoptError
as err:
719 self.
dbg.p(3, f
"***Exception*** {err} at line {get_linenumber()}")
724 locale =
"%s" % QLocale.system().name()
725 qtTranslator = QTranslator()
726 if qtTranslator.load(
"qt_" + locale):
727 app.installTranslator(qtTranslator)
728 appTranslator = QTranslator()
729 langdir = os.path.join(FenetrePrincipale._dir(
"langues"),
730 r"pymecavideo_" + locale)
731 if appTranslator.load(langdir):
732 b = app.installTranslator(appTranslator)
737if __name__ ==
"__main__":
dbg.py, a module for pymecavideo: a program to track moving points in a video frameset
Une classe qui permet de définir les états pour le pointageWidget debut, A, AB, B,...
def changeEtat(self, etat)
changement d'état : fait ce qu'il faut faire au niveau de la fenêtre principale puis renvoie aux autr...
def changeEtat(self, etat)
Mise en place d'un état de l'interface utilisateur, voir la documentation dans le fichier etat_pymeca...
def setupUi(self, pymecavideo)
actionCopier_dans_le_presse_papier
def choix_onglets(self, newIndex)
traite les signaux émis par le changement d'onglet, ou par le changement de référentiel dans l'onglet...
def connecte_ui(self)
connecte les signaux de Qt
def __init__(self, parent=None, opts=[], args=[])
le constructeur reçoit les données principales du logiciel :
def cache_imgdim(self)
Cache le widget d'affichage de la dimension de l'image quand son temps de présentation est révolu.
def closeEvent(self, event)
Un crochet pour y mettre toutes les procédures à faire lors de la fermeture de l'application.
def recommence_echelle(self)
def init_variables(self, filename="")
def setStatus(self, text)
Précise la ligne de statut, qui commence par une indication de l'état.
def ajuste_pour_image(self)
ajuste progressivement la taille de la fenêtre principale jusqu'à ce que l'image soit à la taille vou...
def rouvre(self, fichier)
Rouvre un fichier pymecavideo précédemment enregistré
def defixeLesDimensions(self)
donne une taille minimale à la fenêtre, 640 x 480 ; il s'y ajoute bien sûr les contraintes des widget...
def montre_volet_coord(self)
Met l'onglet des coordonnées sur le dessus.
def apply_preferences(self, rouvre=False)
Récupère les préférences sauvegardées, et en applique les données ici on s'occupe de ce qui se gère f...
def sync_img2others(self, i)
Fait en sorte que self.pointage.horizontalSlider et self.pointage.spinBox_image aient le numéro i.
def fixeLesDimensions(self)
def egalise_origine(self)
harmonise l'origine : recopie celle de la vidéo vers le widget des trajectoires et redessine les deux...
def openfile(self)
Ouvre un dialogue pour choisir un fichier vidéo puis le charge.
def showFullScreen_(self)
gère les dimensions en fonction de la largeur et la hauteur de l'écran
def basculer_plein_ecran(self)
Basculer en mode plein écran / mode fenétré
def traite_arg(self)
traite les arguments donnés à l'application.
def montre_volet_video(self)
Met l'onglet des vidéos sur le dessus.
def _dir(lequel=None, install=None)
def check_prefs_version(self)
Vérifie la version du fichier de préférences.
def redimensionneFenetre(self, tourne=False)
une classe pour des vecteurs 2D ; les coordonnées sont flottantes, et on peut accéder à celles-ci par...