Pymecavideo 8.0
Étude cinématique à l'aide de vidéos
pymecavideo.py
1# -*- coding: utf-8 -*-
2
3import sys,os, locale
4thisDir = os.path.dirname(__file__)
5sys.path.insert(0, thisDir)
6
7import subprocess
8
9from globdef import HOME_PATH, VIDEO_PATH, CONF_PATH, \
10 ICON_PATH, LANG_PATH, \
11 DATA_PATH, HELP_PATH, DOCUMENT_PATH, \
12 pattern_float
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
16
17import getopt
18import traceback
19import time
20import numpy as np
21import math
22import tempfile
23import platform
24import re
25import magic
26
27from export import Export, EXPORT_FORMATS
28from toQimage import toQImage
29from version import version, Version, str2version
30from dbg import Dbg
31from preferences import Preferences
32from trajWidget import trajWidget
33from glob import glob
34from vecteur import vecteur
35from echelle import echelle
36
37import interfaces.icon_rc
38
39licence = {}
40licence['en'] = """
41 pymecavideo version %s:
42
43 a program to track moving points in a video frameset
44
45 Copyright (C) 2007-2016 Jean-Baptiste Butet <ashashiwa@gmail.com>
46
47 Copyright (C) 2007-2023 Georges Khaznadar <georgesk@debian.org>
48
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.
53
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.
58
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/>.
61"""
62
63licence['fr'] = """
64 pymecavideo version %s :
65
66 un programme pour tracer les trajectoires des points dans une vidéo.
67
68 Copyright (C) 2007-2016 Jean-Baptiste Butet <ashashiwa@gmail.com>
69
70 Copyright (C) 2007-2023 Georges Khaznadar <georgesk@debian.org>
71
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.
73
74 Ce programme est fait avec l'espoir qu'il sera utile mais SANS AUCUNE GARANTIE. Lisez la licence pour plus de détails.
75
76 <http://www.gnu.org/licenses/>.
77"""
78#
79# Le module de gestion des erreurs n'est chargé que si on execute le fichier .exe ou si on est sous Linux
80#
81# if sys.platform == "win32" or sys.argv[0].endswith(".exe"):
82# import Error
83thisDir = os.path.dirname(os.path.abspath(__file__))
84sys.path.insert(0, thisDir)
85
86from inspect import currentframe
87
88def get_linenumber():
89 cf = currentframe()
90 return cf.f_back.f_lineno
91
92#from export_python import ExportDialog
93# création précoce de l'objet application, déjà nécessaire pour traiter les bugs
94app = QApplication(sys.argv)
95
96
97#import qtiplotexport
98
99
100from interfaces.Ui_pymecavideo import Ui_pymecavideo
101from etatsMain import Etats
102
104 def __init__(self, parent=None, opts=[], args=[]):
105 """
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
110 """
111 QMainWindow.__init__(self, parent)
112 Ui_pymecavideo.__init__(self)
113 Etats.__init__(self)
114 # version minimale du fichier de configuration :
115 self.min_version = version(7, 3, ".0-1")
116 self.wanted_image_size = vecteur() # taille souhaitée pour l'image
117 self.nb_ajuste_image = 20 # nombre d'itérations pour y parvenir
118 self.imgdim_hide = time.time() + 2 # moment pour cacher la taille
119 self.etatetat = None # avant le système d'états
120
121 # Mode plein écran
122 self.plein_ecran = False
123 QShortcut(QKeySequence("F11"), self, self.basculer_plein_ecran)
124
125 g = QApplication.instance().screens()[0].geometry()
126 self.height_screen, self.width_screen = g.height(), g.width()
127
128 self.setupUi(self)
129
130 self.dbg = Dbg(0)
131 for o in opts:
132 if ('-d' in o[0]) or ('--debug' in o[0]):
133 self.dbg = Dbg(o[1])
134 print(self.dbg)
135
136 self.prefs = Preferences(self)
137
138 # définition des widgets importants
139 self.pointage.setApp(self)
140 self.trajectoire.setApp(self)
141 self.coord.setApp(self)
142 self.graph.setApp(self)
143
144 # on cache le widget des dimensions de l'image
145 self.hide_imgdim.emit()
146
147 self.args = args
148
149 self.platform = platform.system()
150
151 # initialise les répertoires
152 self._dir_dir()
153
154
155 # crée les action exportQactions du menu Fichier
156 for key in sorted(EXPORT_FORMATS.keys()):
157 action = QAction(EXPORT_FORMATS[key]
158 ['nom'], self.menuE_xporter_vers)
159 action.triggered.connect(
160 lambda checked, index=key: self.coord.export(index))
161 self.menuE_xporter_vers.addAction(action)
162
163 self.init_variables(self.prefs.defaults['lastVideo'])
164 # connections internes
165 self.connecte_ui()
166 # met l'état à "debut"
167 self.change_etat.emit("debut")
168
169 # traite un argument ; s'il n'y en a pas, s'intéresse
170 # aux préférences du fichier pymecavideo.conf
171 self.traite_arg() or self.apply_preferences()
172
173 return
174
175 def traite_arg(self):
176 """
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
180 """
181 OK = False
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/"):
187 OK = True
188 self.pointage.openTheFile(filename)
189 elif mt == "text/plain":
190 signature = open(filename).read(24)
191 if signature.startswith("# version = pymecavideo"):
192 OK = True
193 self.rouvre(filename)
194 if not OK:
195 QTimer.singleShot(
196 50,
197 lambda: QMessageBox.information(
198 self,
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)))
201
202 return OK
203
204 def check_prefs_version(self):
205 """
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,
208 2 quand tout va bien
209 """
210 m = re.match(r"pymecavideo (.*)",
211 self.prefs.config["DEFAULT"]["version"])
212 if m:
213 thisversion = str2version(m.group(1))
214 else:
215 thisversion = version(0,0)
216 if thisversion < self.min_version:
217 QTimer.singleShot(
218 50,
219 lambda: QMessageBox.information(
220 self,
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)))
223 return 0
224 elif thisversion < Version:
225 QTimer.singleShot(
226 50,
227 lambda: QMessageBox.information(
228 self,
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)))
231 return 1
232 # le fichier de configuration a la bonne version, on applique ses
233 # données
234 return 2
235
236 def apply_preferences(self, rouvre = False):
237 """
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
240 fenêtre principale
241 @param rouvre est vrai quand on ouvre un fichier pymecavideo ;
242 il est faux par défaut
243 """
244 self.dbg.p(2, "rentre dans 'FenetrePrincipale.apply_preferences'")
245 # on relit les préférences du fichier de configuration, sauf en cas
246 # de réouverture d'un fichier pymecavideo, qui contient les préférences
247 if not rouvre:
248 self.prefs = Preferences(self)
249 if not self.check_prefs_version(): return
250 self.wanted_image_size = self.prefs.config.getvecteur(
251 "DEFAULT", "taille_image")
252 self.nb_ajuste_image = 20 # pas plus de 20 itérations
253 self.adjust4image.emit()
254
255 d = self.prefs.config["DEFAULT"]
256 self.pointage.apply_preferences(rouvre = rouvre)
258 return
259
260 def hasHeightForWidth(self):
261 # This tells the layout manager that the banner's height does depend on its width
262 return True
263
264 def sizeHint(self):
265 return QSize(1024, 768)
266
267 def showFullScreen_(self):
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 ))
270 return
271
272 def basculer_plein_ecran(self):
273 """Basculer en mode plein écran / mode fenétré"""
274 self.dbg.p(2, "rentre dans 'basculer_plein_ecran'")
275 if not self.plein_ecran:
276 self.showFullScreen()
277 else:
278 self.showNormal()
279 self.plein_ecran = not (self.plein_ecran)
280
281 def init_variables(self, filename=""):
282 self.dbg.p(2, "rentre dans 'init_variables'")
283 self.index_max = 1
284 self.repere = 0
285 # contient les listes des abscisses, vitesses, énergies calculées par le grapheur.
286 self.motif = []
287 self.index_du_point = 0
288 self.couleurs = ["red", "blue", "cyan", "magenta", "yellow", "gray",
289 "green"] # correspond aux couleurs des points de la trajectoire
290 self.point_attendu = 0
291 self.nb_clics = 0
292 self.premierResize = True # arrive quand on ouvre la première fois la fenetre
293 self.pointage.index = 1 # image à afficher
294 self.chronoImg = 0
295 self.filename = filename
296 self.stdout_file = os.path.join(CONF_PATH, "stdout")
297 self.exitDecode = False
298 self.resizing = False
299 self.stopRedimensionne = False
300 self.refait_point = False
301 return
302
303
304
305 ############ les signaux spéciaux #####################
306 selection_done = pyqtSignal()
307 redimensionneSignal = pyqtSignal(bool)
308 updateProgressBar = pyqtSignal()
309 change_etat = pyqtSignal(str)
310 show_coord = pyqtSignal() # montre l'onglet des coordonnées
311 show_video = pyqtSignal() # montre l'onglet des vidéos
312 adjust4image = pyqtSignal() # adapte la taille à l'image
313 hide_imgdim = pyqtSignal() # cache la dimension de l'image
314 affiche_statut = pyqtSignal(str) # modifie la ligne de statut
315 stopRedimensionnement = pyqtSignal() # fixe la taille de la fenêtre
316 OKRedimensionnement = pyqtSignal() # libère la taille de la fenêtre
317 image_n = pyqtSignal(int) # modifie les contrôles d'image
318 new_echelle = pyqtSignal() # redemande l'échelle
319
320 def connecte_ui(self):
321 """connecte les signaux de Qt"""
322 self.dbg.p(2, "rentre dans 'connecte_ui'")
323
324 #connexion de signaux de menus
325 self.actionOuvrir_un_fichier.triggered.connect(self.openfile)
326 self.actionExemples.triggered.connect(self.openexample)
327 self.action_propos.triggered.connect(self.propos)
328 self.actionAide.triggered.connect(self.aide)
329 #self.actionDefaire.triggered.connect(self.pointage.efface_point_precedent)
330 self.actionRefaire.triggered.connect(self.pointage.refait_point_suivant)
331 self.actionQuitter.triggered.connect(self.close)
332 self.actionSaveData.triggered.connect(self.pointage.enregistre_ui)
333 self.actionCopier_dans_le_presse_papier.triggered.connect(
334 self.coord.presse_papier)
335 self.actionRouvrirMecavideo.triggered.connect(self.rouvre_ui)
336
337 # connexion de signaux de widgets
338 self.tabWidget.currentChanged.connect(self.choix_onglets)
339
340 # connexion de signaux spéciaux
342 self.updateProgressBar.connect(self.updatePB)
343 self.change_etat.connect(self.changeEtatchangeEtat)
344 self.show_coord.connect(self.montre_volet_coord)
345 self.show_video.connect(self.montre_volet_video)
346 self.affiche_statut.connect(self.setStatus)
347 self.adjust4image.connect(self.ajuste_pour_image)
348 self.hide_imgdim.connect(self.cache_imgdim)
351 self.image_n.connect(self.sync_img2others)
352 self.new_echelle.connect(self.recommence_echelle)
353 return
354
355 def cache_imgdim(self):
356 """
357 Cache le widget d'affichage de la dimension de l'image
358 quand son temps de présentation est révolu
359 """
360 if time.time() > self.imgdim_hide:
361 self.pointage.imgdimEdit.hide()
362 else:
363 QTimer.singleShot(200, self.cache_imgdim)
364 return
365
366 def ajuste_pour_image(self):
367 """
368 ajuste progressivement la taille de la fenêtre principale
369 jusqu'à ce que l'image soit à la taille voulue, c'est à dire
371 """
372 delai = 50 # 20 ajustements par seconde
373 w1, h1 = self.width(), self.height()
374 self.OKRedimensionnement.emit()
375
376 def modifie(x, y):
377 """
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
380 """
381 def m():
382 self.resize(w1 + x, h1 + y)
383 self.update()
384 self.adjust4image.emit()
385 return
386 return m
387
388 # taille de l'image actuellement
389 w, h = int(self.pointage.video.size().width()), int(self.pointage.video.size().height())
390 # taille visée
391 w0 , h0 = int(self.wanted_image_size.x), int(self.wanted_image_size.y)
392 # erreurs de taille de la fenêtre
393 deltaw, deltah = w0 - w, h0 - h
394 # modifications à apporter à la taille de la fenêtre
395 w2, h2 = 0, 0
396 # on divise par deux l'erreur à compenser, en prenant soin
397 # de ne jamais obtenir zéro ; ça doit faire au moins +-1
398 if deltaw :
399 w2 = int(abs(deltaw)/deltaw*math.ceil(abs(deltaw/2)))
400 if deltah:
401 h2 = int(abs(deltah)/deltah*math.ceil(abs(deltah/2)))
402 if w2 or h2:
403 # enfin on prévoit de modifier la fenêtre dans le bons sens
404 self.nb_ajuste_image -= 1
405 if self.nb_ajuste_image:
406 #seulement si on a encore droit à une itération
407 QTimer.singleShot(delai, modifie(w2, h2))
408 return
409 else:
410 # fini : il n'y a plus besoin de modifier la taille de la fenêtre
411 self.pointage.montre_etalon.emit()
412 if self.pointage.etat not in ("debut", "A"):
413 self.stopRedimensionnement.emit()
414
415 return
416
417 def egalise_origine(self):
418 """
419 harmonise l'origine : recopie celle de la vidéo vers le
420 widget des trajectoires et redessine les deux.
421 """
422 self.trajectoire.trajW.origine_mvt = self.pointage.origine
423 self.trajectoire.update()
424 self.pointage.update()
425 return
426
427 def updatePB(self):
428 self.qmsgboxencode.updateProgressBar()
429
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
435 """
436 if lequel == "home":
437 return HOME_PATH
438 elif lequel == "videos":
439 return VIDEO_PATH
440 elif lequel == "conf":
441 return CONF_PATH
442 elif lequel == "icones":
443 return ICON_PATH
444 elif lequel == "langues":
445 return LANG_PATH
446 elif lequel == "data":
447 return DATA_PATH
448 elif lequel == "help":
449 return HELP_PATH
450 elif type(lequel) == type(""):
451 self.dbg.p(
452 1, "erreur, appel de _dir() avec le paramètre inconnu %s" % lequel)
453 self.close()
454 else:
455 # vérifie/crée les repertoires
456 dd = FenetrePrincipale._dir("conf")
457 if not os.path.exists(dd) and \
458 os.access(os.path.dirname(dd), os.W_OK):
459 os.makedirs(dd)
460 _dir = staticmethod(_dir)
461
462 def rouvre_ui(self):
463 self.dbg.p(2, "rentre dans 'rouvre_ui'")
464
465 dir_ = DOCUMENT_PATH
466 fichier, _ = QFileDialog.getOpenFileName(
467 self,
468 self.tr("Ouvrir un projet Pymecavideo"),
469 dir_,
470 self.tr("Projet Pymecavideo (*.mecavideo)"))
471 if fichier != "":
472 self.rouvre(fichier)
473 return
474
475 def redimensionneFenetre(self, tourne=False):
476 self.dbg.p(2, "rentre dans 'redimensionneFenetre'")
477 if tourne: # on vient de cliquer sur tourner. rien n'est changé.
478 self.dbg.p(2, "Dans 'redimensionneFenetre', tourne")
479 self.pointage.remontre_image()
480 else:
481 self.pointage.affiche_image()
482 self.trajectoire.trajW.maj()
483 return
484
485 def resizeEvent(self, event):
486 self.dbg.p(2, "rentre dans 'resizeEvent'")
487
488 # on montre la taille de l'image
489 self.pointage.imgdimEdit.show()
490 # pour deux secondes de plus
491 self.imgdim_hide = time.time() + 2
492 # et on en programme l'extinction
493 self.hide_imgdim.emit()
494
495 self.redimensionneFenetre(tourne=False)
496 return super(FenetrePrincipale, self).resizeEvent(event)
497
498 def showEvent(self, event):
499 self.dbg.p(2, "rentre dans 'showEvent'")
500 self.redimensionneSignal.emit(False)
501
502 def choix_onglets(self, newIndex):
503 """
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
507 du tabWidget
508 """
509 self.dbg.p(2, f"rentre dans 'choix_onglets', self.tabWidget.currentIndex() = {self.tabWidget.currentIndex()}, newIndex = {newIndex}")
510 self.statusBar().clearMessage()
511 if self.tabWidget.currentIndex() == 0:
512 # onglet video, on ne fait rien de spécial
513 pass
514 if self.tabWidget.currentIndex() == 1:
515 # onglet des trajectoires
516 self.trajectoire.trace.emit("absolu")
517 elif self.tabWidget.currentIndex() == 2:
518 # onglet des coordonnées
519 self.coord.affiche_tableau()
520 elif self.tabWidget.currentIndex() == 3:
521 # onglet du grapheur
522 self.graph.affiche_grapheur()
523 return
524
525 def recommence_echelle(self):
526 self.dbg.p(2, "rentre dans 'recommence_echelle'")
527 self.tabWidget.setCurrentIndex(0)
528 self.pointage.echelle_image = echelle()
529 self.pointage.affiche_echelle()
530 self.pointage.demande_echelle()
531 return
532
533 def closeEvent(self, event):
534 """
535 Un crochet pour y mettre toutes les procédures à faire lors
536 de la fermeture de l'application.
537 """
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)
542 self.prefs.save()
543 return
544
545 def openexample(self):
546 self.dbg.p(2, "rentre dans 'openexample'")
547 dir_ = "%s" % (self._dir_dir("videos"))
548 filename, hints = QFileDialog.getOpenFileName(
549 self,
550 self.tr("Ouvrir une vidéo"), dir_,
551 self.tr("fichiers vidéos (*.avi *.mp4 *.ogv *.mpg *.mpeg *.ogg *.mov *.wmv)"))
552 self.pointage.openTheFile(filename)
553 return
554
555 def openfile(self):
556 """
557 Ouvre un dialogue pour choisir un fichier vidéo puis le charge
558 """
559 self.dbg.p(2, "rentre dans 'openfile'")
560 dir_ = self._dir_dir("videos")
561 filename, hints = QFileDialog.getOpenFileName(
562 self,
563 self.tr("Ouvrir une vidéo"),
564 dir_,
565 self.tr("fichiers vidéos ( *.avi *.mp4 *.ogv *.mpg *.mpeg *.ogg *.wmv *.mov)"))
566 self.pointage.openTheFile(filename)
567 self.pointage.reinitialise_capture()
568 return
569
570 def renomme_le_fichier(self):
571 self.dbg.p(2, "rentre dans 'renomme_le_fichier'")
572 renomme_fichier = QMessageBox.warning(
573 self,
574 self.tr("Nom de fichier non conforme"),
575 self.tr("""\
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(
579 self,
580 self.tr("Ouvrir une vidéo"),
581 self._dir_dir("videos", None),
582 "*.avi")
583 self.pointage.openTheFile(filename)
584 return
585
586
587 def propos(self):
588 self.dbg.p(2, "rentre dans 'propos'")
589 try:
590 loc = locale.getdefaultlocale()[0][0:2]
591 except TypeError as err:
592 self.dbg.p(3, f"***Exception*** {err} at line {get_linenumber()}")
593 loc = ''
594 if loc in licence.keys():
595 licence_XX = licence[loc] % Version
596 else:
597 licence_XX = licence["en"] % Version
598 QMessageBox.information(
599 None,
600 self.tr("Licence"),
601 licence_XX)
602 return
603
604 def aide(self):
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)
611 if status != 0:
612 command = "x-www-browser %s" % helpfile
613 status = subprocess.call(command, shell=True)
614 else:
615 QMessageBox.warning(
616 None,
617 self.tr("Aide"),
618 self.tr("Désolé pas de fichier d'aide pour le langage {0}.").format(lang))
619 return
620
621 def rouvre(self, fichier):
622 """
623 Rouvre un fichier pymecavideo précédemment enregistré
624 """
625 self.dbg.p(2, "rentre dans 'rouvre'")
626
627 lignes = open(fichier).readlines()
628 # on récupère les données importantes
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))
633 self.apply_preferences(rouvre=True)
634
635 # donne la main au videoWidget pour préparer les pointages
636 self.pointage.rouvre()
637 lignes_data = [l for l in lignes if l[0] != "#" and len(l.strip()) > 0]
638 # on trouve les données en coupant là où il y a des séparations
639 # par des espaces ou des tabulations, on ne conserve pas la
640 # première ligne qui commence par "t x1 y1 ..." et aussi
641 # on remplace les virgules décimales par des points
642 data = [re.split(r"\s+", l.strip().replace(",", "."))
643 for l in lignes_data][1:]
644 self.pointage.restaure_pointages(
645 data, self.prefs.config["DEFAULT"].getint("index_depart"))
646 self.sync_img2others(self.pointage.index)
647 self.change_etat.emit("D")
648 return
649
650 def sync_img2others(self, i):
651 """
652 Fait en sorte que self.pointage.horizontalSlider et self.pointage.spinBox_image
653 aient le numéro i
654 @param i le nouvel index
655 """
656 self.pointage.spinBox_image.setValue(i)
657 self.pointage.horizontalSlider.setValue(i)
658 return
659
661 """
662 Met l'onglet des coordonnées sur le dessus
663 """
664 self.tabWidget.setCurrentIndex(2)
665 return
666
667 def montre_volet_video(self):
668 """
669 Met l'onglet des vidéos sur le dessus
670 """
671 self.tabWidget.setCurrentIndex(0)
672 return
673
674 def setStatus(self, text):
675 """
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
678 """
679 if self.etatetat is None : return
680 self.statusBar().showMessage(self.roleEtat[self.etatetat](self) + "| " + text)
681 return
682
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()))
688 return
689
690 def defixeLesDimensions(self):
691 """
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.
695 """
696 self.setMinimumWidth(0)
697 self.setMaximumWidth(16000000)
698 self.setMinimumHeight(0)
699 self.setMaximumHeight(16000000)
700 return
701
702def usage():
703 print("""\
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
708
709 les fichiers mecavideo sont créés par l'application pymecavideo""")
710 return
711
712
713def run():
714 global app
715 args = sys.argv[1:]
716 try:
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()}")
720 usage()
721 sys.exit(2)
722
723 ###translation##
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)
733 window = FenetrePrincipale(None, opts, args)
734 window.show()
735 sys.exit(app.exec())
736
737if __name__ == "__main__":
738 run()
dbg.py, a module for pymecavideo: a program to track moving points in a video frameset
Definition: dbg.py:26
Une classe qui permet de définir les états pour le pointageWidget debut, A, AB, B,...
Definition: etatsMain.py:35
def changeEtat(self, etat)
changement d'état : fait ce qu'il faut faire au niveau de la fenêtre principale puis renvoie aux autr...
Definition: etatsMain.py:45
def changeEtat(self, etat)
Mise en place d'un état de l'interface utilisateur, voir la documentation dans le fichier etat_pymeca...
Definition: etats.py:47
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...
Definition: pymecavideo.py:513
def connecte_ui(self)
connecte les signaux de Qt
Definition: pymecavideo.py:321
def __init__(self, parent=None, opts=[], args=[])
le constructeur reçoit les données principales du logiciel :
Definition: pymecavideo.py:110
def cache_imgdim(self)
Cache le widget d'affichage de la dimension de l'image quand son temps de présentation est révolu.
Definition: pymecavideo.py:359
def closeEvent(self, event)
Un crochet pour y mettre toutes les procédures à faire lors de la fermeture de l'application.
Definition: pymecavideo.py:542
def init_variables(self, filename="")
Definition: pymecavideo.py:281
def setStatus(self, text)
Précise la ligne de statut, qui commence par une indication de l'état.
Definition: pymecavideo.py:683
def ajuste_pour_image(self)
ajuste progressivement la taille de la fenêtre principale jusqu'à ce que l'image soit à la taille vou...
Definition: pymecavideo.py:371
def rouvre(self, fichier)
Rouvre un fichier pymecavideo précédemment enregistré
Definition: pymecavideo.py:629
def defixeLesDimensions(self)
donne une taille minimale à la fenêtre, 640 x 480 ; il s'y ajoute bien sûr les contraintes des widget...
Definition: pymecavideo.py:700
def montre_volet_coord(self)
Met l'onglet des coordonnées sur le dessus.
Definition: pymecavideo.py:668
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...
Definition: pymecavideo.py:243
def sync_img2others(self, i)
Fait en sorte que self.pointage.horizontalSlider et self.pointage.spinBox_image aient le numéro i.
Definition: pymecavideo.py:660
def egalise_origine(self)
harmonise l'origine : recopie celle de la vidéo vers le widget des trajectoires et redessine les deux...
Definition: pymecavideo.py:421
def openfile(self)
Ouvre un dialogue pour choisir un fichier vidéo puis le charge.
Definition: pymecavideo.py:563
def showFullScreen_(self)
gère les dimensions en fonction de la largeur et la hauteur de l'écran
Definition: pymecavideo.py:268
def basculer_plein_ecran(self)
Basculer en mode plein écran / mode fenétré
Definition: pymecavideo.py:273
def traite_arg(self)
traite les arguments donnés à l'application.
Definition: pymecavideo.py:180
def montre_volet_video(self)
Met l'onglet des vidéos sur le dessus.
Definition: pymecavideo.py:675
def _dir(lequel=None, install=None)
Definition: pymecavideo.py:437
def check_prefs_version(self)
Vérifie la version du fichier de préférences.
Definition: pymecavideo.py:209
def redimensionneFenetre(self, tourne=False)
Definition: pymecavideo.py:480
une classe pour des vecteurs 2D ; les coordonnées sont flottantes, et on peut accéder à celles-ci par...
Definition: vecteur.py:44