def __init__(self, parent): super(EkdTamponIn, self).__init__(parent) self.parent = parent # Lien avec EKD main. self.setWindowTitle(QString(_(u"Réserve de fichiers sources - tampon"))) self.layout = QVBoxLayout(self) # Layout vertical pour placer les éléments ### # Tab Vidéo | Tab Image | Tab Audio # ... # ---------------------------------------------------- # Boutons d'action (ajouter, supprimer, importer, aide) self.parent = parent self.geometry = self.parent.frameGeometry # Création du TabWidget + 3 Widgets de sélection de fichiers par type self.tab = QTabWidget() self.selectVideo = SelectWidget(mode="texte", video=True) vidIcon = QIcon("Icones/icone_video_128.png") self.tab.addTab(self.selectVideo, vidIcon, _(u"Vidéo")) self.selectImage = SelectWidget(mode="icone", geometrie=self.geometry) self.tab.addTab(self.selectImage, QIcon("Icones/icone_images_128.png"), _(u"Images")) self.selectAudio = SelectWidget(mode="texte", audio=True) self.tab.addTab(self.selectAudio, QIcon("Icones/icone_sons_128.png"), _(u"Audio")) # Ajout du TabWidget dans le layout self.layout.addWidget(self.tab) # Layout pour accueillir les boutons d'action HLayout = QHBoxLayout() # Boutons d'actions + spacer pour aligner' self.aide = QPushButton(QIcon("Icones/icone_aide_128.png"), _(u"Aide")) self.exportplus = QPushButton(QIcon("Icones" + os.sep + "icone_ajout_plus.png"), _(u"Ajouter")) self.exportreplace = QPushButton(QIcon("Icones" + os.sep + "icone_ajout_replace.png"), _(u"Remplacer")) self.importFile = QPushButton(QIcon("Icones" + os.sep + "icone_save_128.png"), _(u"Importer")) self.retour = QPushButton(QIcon("Icones/revenir.png"), _(u"Revenir")) HLayout.addWidget(self.aide) HLayout.addWidget(self.exportplus) HLayout.addWidget(self.exportreplace) HLayout.addWidget(self.importFile) HLayout.addStretch() HLayout.addWidget(self.retour) self.layout.addLayout(HLayout) ### TODO : Ajouter les connect des boutons (Au minimum Help, et retour, les autres, c'est à voir comment on va intégrer) self.connect(self.aide, SIGNAL("clicked()"), self.afficherAide) self.connect(self.retour, SIGNAL("clicked()"), self.hide) self.connect(self.exportplus, SIGNAL("clicked()"), self.copyToTab) self.connect(self.exportreplace, SIGNAL("clicked()"), self.copyToTabRep) self.connect(self.importFile, SIGNAL("clicked()"), self.copyFromOutput)
def __init__(self, verifsformats, parent=None): super(Videoporama_Main, self).__init__(boite='vbox', titre=_(u"Diaporama d'images en vidéo")) #=== Drapeaux ===# # Une conversion (même partielle) a-t-elle eu lieu après le chargement des images? (1: vrai) # Est-ce que des images sources ont été modifiées? (c'est-à-dire ajoutées ou supprimées) self.modifImageSource = 0 # Fonction appelant la fenêtre principale self.mainWindowFrameGeometry = parent.frameGeometry layout = QVBoxLayout() self.tabW=QTabWidget() self.afficheurImgSource=SelectWidget(geometrie = self.mainWindowFrameGeometry) self.dicoTab = {} self.dicoTab['images']=self.add(self.afficheurImgSource, _(u'Image(s) source(s)')) self.idSection = 'videoporama' ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "image" # Défini le type de fichier source. self.typeSortie = "video" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurImgSource # Fait le lien avec le sélecteur de fichier source. #=== Widget réglages videoporama ===# self.vid_widg=vid_widg(verifsformats, parent) # Class vid_widg est dans videoporama_widget.py self.add(self.vid_widg,_(u"Réglages")) self.addPreview(light=True) self.addLog() #---------------------------------------------------------------------------------------------------- # Signal de présence d'images dans ler widget de sélection -> modifie le statut des boutons d'action #---------------------------------------------------------------------------------------------------- self.connect(self.afficheurImgSource, SIGNAL("pictureChanged(int)"), self.modifBoutonsAction)
class AnimationEncodageAVCHD(Base) : "Cadre de Animation -> Encodage -> Gestion AVCHD. But: encodages spécifiques au départ du format AVCHD haute définition (Caméra digitale HD)" def __init__(self): #=== Variable de configuration ===# self.config=EkdConfig # Identifiant de la classe self.idSection = "animation_encodage_avchd" # idSection à modifier lorsqu'il y aura des données dans la config. super(AnimationEncodageAVCHD, self).__init__('hbox', titre=_(u"Transcodage: Gestion de l'AVCHD")) self.printSection() # Customisation de l'interface aux besoins de l'AVCHD # 1. Onglet sources self.afficheurVideoSource=SelectWidget(extensions = ["*.m2ts", "*.mts", "*.m2t", "*.mp4"], mode="texte", video = True) # Onglets self.indexVideoSource = self.add(self.afficheurVideoSource, _(u'Video(s) source')) self.connect(self.afficheurVideoSource,SIGNAL("fileSelected"), self.getFile) self.connect(self.afficheurVideoSource, SIGNAL("pictureChanged(int)"), self.getFile) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "video" # Défini le type de fichier source. self.typeSortie = "video" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurVideoSource # Fait le lien avec le sélecteur de fichier source. # 2. Onglet réglages groupReglage = QGroupBox() self.layoutReglage = QVBoxLayout(groupReglage) ##=== Widget qui seront inclus dans la boite de réglage ===# layoutSortie = QHBoxLayout() sortie = QLabel(_(u"Encodage sortie vidéo :")) layoutSortie.addWidget(sortie) # self.codecSortie = QComboBox() liste_codecs = ["MOV (.mov)", "VOB (.vob)", "MPEG2 (.mpg)", "MPEG1 (.mpg)", "MPEG4 (.mp4)", "WMV2 (.wmv)", "HFYU (yuv422p) (.avi)", "MSMPEG 4 version 2 (.avi)", "Motion JPEG (.avi)", "FFV1 (FFmpeg) (.avi)", "Avid DNxHD (.mov)"] codecs = QStringList([QString(f) for f in liste_codecs]) # self.codecSortie.addItems(codecs) layoutSortie.addWidget(self.codecSortie) self.layoutReglage.addLayout(layoutSortie) self.connect(self.codecSortie, SIGNAL("currentIndexChanged(int)"), self.changerReglagesCodec) self.connect(self.codecSortie, SIGNAL("activated(QString)"), self.selection) # Widgets pour la sélection de la résolution en sortie --------------------- layoutReso = QHBoxLayout() self.resolu = QLabel(_(u"Résolution sortie :")) layoutReso.addWidget(self.resolu) self.resoSortie = QComboBox() liste_reso = ["1920x1080", "1440x1080", "1280x720", "720x576"] reso = QStringList([QString(r) for r in liste_reso]) self.resoSortie.addItems(reso) layoutReso.addWidget(self.resoSortie) self.layoutReglage.addLayout(layoutReso) self.connect(self.resoSortie, SIGNAL("currentIndexChanged(int)"), self.changerReglagesResol) ### Ajouté le 13/08/2010 ################################################### # Widgets pour la sélection des spécificités DNxHD ------------------------- layoutSpec_DNxHD = QHBoxLayout() self.label_spec_DNxHD = QLabel(_(u"Spécificités :")) layoutSpec_DNxHD.addWidget(self.label_spec_DNxHD) self.specSortie_DNxHD = QComboBox() liste_spec_DNxHD = [_(u"Dimension:1920x1080 Img/sec:29.97 Bitrate:220 Mb/s"), _(u"Dimension:1920x1080 Img/sec:29.97 Bitrate:145 Mb/s"), _(u"Dimension:1920x1080 Img/sec:25 Bitrate:185 Mb/s"), _(u"Dimension:1920x1080 Img/sec:25 Bitrate:120 Mb/s"), _(u"Dimension:1920x1080 Img/sec:25 Bitrate:36 Mb/s"), _(u"Dimension:1920x1080 Img/sec:24 Bitrate:175 Mb/s"), _(u"Dimension:1920x1080 Img/sec:24 Bitrate:115 Mb/s"), _(u"Dimension:1920x1080 Img/sec:24 Bitrate:36 Mb/s"), _(u"Dimension:1920x1080 Img/sec:23.976 Bitrate:175 Mb/s"), _(u"Dimension:1920x1080 Img/sec:23.976 Bitrate:115 Mb/s"), _(u"Dimension:1920x1080 Img/sec:23.976 Bitrate:36 Mb/s"), _(u"Dimension:1920x1080 Img/sec:29.97 Bitrate:220 Mb/s"), _(u"Dimension:1920x1080 Img/sec:29.97 Bitrate:145 Mb/s"), _(u"Dimension:1920x1080 Img/sec:29.97 Bitrate:45 Mb/s"), _(u"Dimension:1280x720 Img/sec:59.94 Bitrate:220 Mb/s"), _(u"Dimension:1280x720 Img/sec:59.94 Bitrate:145 Mb/s"), _(u"Dimension:1280x720 Img/sec:50 Bitrate:175 Mb/s"), _(u"Dimension:1280x720 Img/sec:50 Bitrate:115 Mb/s"), _(u"Dimension:1280x720 Img/sec:29.97 Bitrate:110 Mb/s"), _(u"Dimension:1280x720 Img/sec:29.97 Bitrate:75 Mb/s"), _(u"Dimension:1280x720 Img/sec:25 Bitrate:90 Mb/s"), _(u"Dimension:1280x720 Img/sec:25 Bitrate:60 Mb/s"), _(u"Dimension:1280x720 Img/sec:23.976 Bitrate:90 Mb/s"), _(u"Dimension:1280x720 Img/sec:23.976 Bitrate:60 Mb/s")] spec_DNxHD = QStringList([QString(spec_DNxHD) for spec_DNxHD in liste_spec_DNxHD]) self.specSortie_DNxHD.addItems(spec_DNxHD) layoutSpec_DNxHD.addWidget(self.specSortie_DNxHD) self.layoutReglage.addLayout(layoutSpec_DNxHD) self.connect(self.specSortie_DNxHD, SIGNAL("currentIndexChanged(int)"), self.changerReglagesSpec_DNxHD) # Par défaut ces 2 widgets sont invisibles (comme ça ils n'apparaissent pas # dans le cas d'une sélection autre que Avid DNxHD (.mov)) self.label_spec_DNxHD.hide() self.specSortie_DNxHD.hide() # Widgets pour la sélection du flux audio en sortie uniquement DNxHD ------- layoutSon_DNxHD = QHBoxLayout() self.label_son_DNxHD = QLabel(_(u"Sortie flux audio :")) layoutSon_DNxHD.addWidget(self.label_son_DNxHD) self.sonSortie_DNxHD = QComboBox() liste_son_DNxHD = [_(u"Copie du flux audio"), _(u"Flux audio PCM sur 2 canaux (stereo)"), _(u"Pas de flux audio")] son_DNxHD = QStringList([QString(son_DNxHD) for son_DNxHD in liste_son_DNxHD]) self.sonSortie_DNxHD.addItems(son_DNxHD) layoutSon_DNxHD.addWidget(self.sonSortie_DNxHD) self.layoutReglage.addLayout(layoutSon_DNxHD) self.connect(self.sonSortie_DNxHD, SIGNAL("currentIndexChanged(int)"), self.changerReglagesSon_DNxHD) # Par défaut ces 2 widgets sont invisibles (comme ça ils n'apparaissent pas # dans le cas d'une sélection autre que Avid DNxHD (.mov)) self.label_son_DNxHD.hide() self.sonSortie_DNxHD.hide() ############################################################################ # Widgets pour la sélection du nombre d'images/seconde en sortie ----------- layoutNbrIm = QHBoxLayout() self.labelNbrImage = QLabel(_(u"Nombre d'images/seconde (entre 2 et 60) :")) layoutNbrIm.addWidget(self.labelNbrImage) self.nbrImage = QSpinBox() self.nbrImage.setRange(2, 60) self.nbrImage.setValue(25) layoutNbrIm.addWidget(self.nbrImage) self.layoutReglage.addLayout(layoutNbrIm) self.connect(self.nbrImage, SIGNAL("valueChanged(int)"), self.changerReglagesNbrImgSec) # Widgets pour la sélection de la qualité de la vidéo en sortie ------------ layoutQualite = QHBoxLayout() self.labelQualite = QLabel(_(u"Qualité de la vidéo (2:bonne, 31:mauvaise) :")) layoutQualite.addWidget(self.labelQualite) self.qualite = QSpinBox() self.qualite.setRange(2, 31) self.qualite.setValue(2) layoutQualite.addWidget(self.qualite) self.layoutReglage.addLayout(layoutQualite) self.connect(self.qualite, SIGNAL("valueChanged(int)"), self.changerReglagesQual) ## On charge les options depuis EkdConfig self.loadOptions() ## self.add(groupReglage, _(u"Réglages")) # 3 et 4 Onglet prévisualisation et info. self.addPreview() self.addLog() def changerReglagesCodec(self): """Fonction pour collecte du réglage du codec en sortie""" self.select_codec = self.codecSortie.currentText() #print 'Codec sélectionné en sortie:', self.select_codec EkdPrint(u'Codec sélectionné en sortie: %s' % str(self.select_codec)) EkdConfig.set(self.idSection, 'codec', self.codecSortie.currentIndex()) def changerReglagesResol(self): "Fonction pour collecte du réglage de la résolution en sortie" # Afficher le changement de sélection dans le combo # de sélection de la résolution en sortie self.select_resolution = self.resoSortie.currentText() resolution = EkdConfig.set(self.idSection, 'resolution', self.resoSortie.currentIndex()) ### Ajouté le 13/08/2010 ################################################### def changerReglagesSpec_DNxHD(self): "Fonction pour collecte du réglage des spécificités pour l'Avid DNxHD" # Afficher le changement de sélection dans le combo de # sélection des spécificités en sortie pour l'Avid DNxHD self.select_spec_DNxHD = self.specSortie_DNxHD.currentText() spec_DNxHD = EkdConfig.set(self.idSection, 'spec_DNxHD', self.specSortie_DNxHD.currentIndex()) def changerReglagesSon_DNxHD(self): "Fonction pour collecte du réglage du flux audio en sortie uniquement pour l'Avid DNxHD" # Afficher le changement de sélection dans le combo de # sélection du flux audio en sortie pour l'Avid DNxHD self.select_son_DNxHD = self.sonSortie_DNxHD.currentText() son_DNxHD = EkdConfig.set(self.idSection, 'audio_DNxHD', self.sonSortie_DNxHD.currentIndex()) ############################################################################ def changerReglagesNbrImgSec(self): "Fonction pour collecte du nombre d'images/seconde en sortie" # Afficher la valeur choisie par l'utilisateur du nbre d'img/sec self.select_nbreImgSec = self.nbrImage.value() #print "Valeur du nombre d'images par seconde:", self.select_nbreImgSec EkdPrint(u"Valeur du nombre d'images par seconde: %s" % self.select_nbreImgSec) EkdConfig.set(self.idSection, 'images_par_seconde', self.select_nbreImgSec) def changerReglagesQual(self): "Fonction pour collecte de la qualité de la vidéo en sortie" # Afficher la valeur choisie par l'utilisateur de la qualité de la vidéo self.select_qualite = self.qualite.value() #print "Valeur de la qualité de la vidéo:", self.select_qualite EkdPrint(u"Valeur de la qualité de la vidéo: %s" % self.select_qualite) EkdConfig.set(self.idSection, 'qualite', self.select_qualite) def selection(self): "Fonction pour affichage/masquage des widgets selon la sélection des codecs en sortie" # Le label d'info et le spinbox sont visibles dans la situation par défaut if self.codecSortie.currentText() in ["MOV (.mov)", "MPEG4 (.mp4)", "WMV2 (.wmv)", "HFYU (yuv422p) (.avi)", "MSMPEG 4 version 2 (.avi)", "Motion JPEG (.avi)", "FFV1 (FFmpeg) (.avi)"]: self.label_spec_DNxHD.hide() # Ajouté le 13/08/2010 self.specSortie_DNxHD.hide() # Ajouté le 13/08/2010 self.label_son_DNxHD.hide() # Ajouté le 13/08/2010 self.sonSortie_DNxHD.hide() # Ajouté le 13/08/2010 self.labelNbrImage.show() self.nbrImage.show() self.resolu.show() # Ajouté le 13/08/2010 self.resoSortie.show() # Ajouté le 13/08/2010 self.labelQualite.show() self.qualite.show() # Si l'utilisateur sélectionne VOB, MPEG1 ou MPEG2 dans l'encodage # final, le label d'info et le spinbox associé deviennent invisibles elif self.codecSortie.currentText() in ["VOB (.vob)", "MPEG1 (.mpg)", "MPEG2 (.mpg)"]: self.label_spec_DNxHD.hide() # Ajouté le 13/08/2010 self.specSortie_DNxHD.hide() # Ajouté le 13/08/2010 self.labelNbrImage.hide() self.nbrImage.hide() self.label_son_DNxHD.hide() # Ajouté le 13/08/2010 self.sonSortie_DNxHD.hide() # Ajouté le 13/08/2010 self.resolu.show() # Ajouté le 13/08/2010 self.resoSortie.show() # Ajouté le 13/08/2010 self.labelQualite.show() self.qualite.show() ### Ajouté le 13/08/2010 ################################################### # Si l'utilisateur sélectionne Avid DNxHD dans l'encodage final, # le label d'info et le spinbox associé deviennent invisibles elif self.codecSortie.currentText() in ["Avid DNxHD (.mov)"]: self.resolu.hide() self.resoSortie.hide() self.labelNbrImage.hide() self.nbrImage.hide() self.labelQualite.hide() self.qualite.hide() self.label_spec_DNxHD.show() self.specSortie_DNxHD.show() self.label_son_DNxHD.show() self.sonSortie_DNxHD.show() ############################################################################ def getFile(self): self.chemin = self.afficheurVideoSource.getFiles() #self.chemin est une list comprenant le ou les fichiers sélectionnés. self.boutApp.setEnabled(True) self.mplayer.setEnabled(True) self.mplayer.setVideos(self.chemin) self.emit(SIGNAL("loaded")) return self.chemin def appliquer(self, nomSortie=None, ouvert=1): "Appel du moteur du cadre" if not nomSortie: chemin = self.getFile() self.chemins = chemin # suffix du codec actif (et collecte de l'extension en sortie) if self.codecSortie.currentText() == "MOV (.mov)": self.suffix_sortie = '.mov' elif self.codecSortie.currentText() == "VOB (.vob)": self.suffix_sortie = '.vob' elif self.codecSortie.currentText() == "MPEG4 (.mp4)": self.suffix_sortie = '.mp4' elif self.codecSortie.currentText() == "WMV2 (.wmv)": self.suffix_sortie = '.wmv' ### Ajouté le 13/08/2010 ################################################### elif self.codecSortie.currentText() == "Avid DNxHD (.mov)": self.suffix_sortie = '.mov' ############################################################################ elif self.codecSortie.currentText() in ["MPEG1 (.mpg)", "MPEG2 (.mpg)"]: self.suffix_sortie = '.mpg' else: self.suffix_sortie = '.avi' # Sélection du codec pour la sortie self.codec_sortie = str(self.codecSortie.currentText()) # Sélection de la résolution pour la vidéo en sortie (Largeur x Hauteur) resolution_sortie_texte = str(self.resoSortie.currentText()) resol_l_h = resolution_sortie_texte.split('x') self.reso_largeur_sortie = str(resol_l_h[0]) self.reso_hauteur_sortie = str(resol_l_h[1]) #print 'Largeur', self.reso_largeur_sortie, type(self.reso_largeur_sortie) EkdPrint(u'Largeur %s %s' % (self.reso_largeur_sortie, type(self.reso_largeur_sortie))) #print 'Hauteur', self.reso_hauteur_sortie, type(self.reso_hauteur_sortie) EkdPrint(u'Hauteur %s %s' % (self.reso_hauteur_sortie, type(self.reso_hauteur_sortie))) # Nombre d'images par secondes sélectionnées pour la sortie self.nbreImgSec_sortie = str(self.nbrImage.value()) # Valeur de la qualité de la vidéo pour la sortie self.qualite_sortie = str(self.qualite.value()) ### Ajouté le 13/08/2010 ################################################### # Spécifications Avid DNxHD (.mov) self.spec_sortie_DNxHD = str(self.specSortie_DNxHD.currentText()) # Choix du flux audio en sortie pour Avid DNxHD self.son_sortie_DNxHD = str(self.sonSortie_DNxHD.currentText()) ############################################################################ # suffix du codec actif saveDialog = EkdSaveDialog(self, mode="video", suffix=self.suffix_sortie, title=_(u"Sauver")) self.cheminFichierEnregistrerVideo = saveDialog.getFile() # A revoir mais là le fichier est nommé comme il faut (!) pre_cheminFichierEnregistrerVideo = self.cheminFichierEnregistrerVideo.split('.') self.cheminFichierEnregistrerVideo = pre_cheminFichierEnregistrerVideo[0] else: # module séquentiel self.cheminFichierEnregistrerVideo = nomSortie if not self.cheminFichierEnregistrerVideo: return # quel est l'index du dernier item sélectionné de la boîte de combo? #print 'index du codec sélectionné', self.codecSortie.currentIndex() EkdPrint(u'index du codec sélectionné %s' % self.codecSortie.currentIndex()) ### Rectification le 14/08/2010 ############################################ if self.codecSortie.currentText() == "Avid DNxHD (.mov)": # Affectation des valeurs de nouvelle largeur et nouvelle hauteur # sélectionnées par l'utilisateur pour le traitement final self.reso_largeur_sortie = "" self.reso_hauteur_sortie = "" # self.qualite_sortie = "" elif self.codecSortie.currentText() != "Avid DNxHD (.mov)": # Si l'utilisateur sélectionne une autre option que Avid DNxHD (.mov) # ces valeurs sont définies comme vides (ça fonctionne bien) self.spec_sortie_DNxHD = "" self.son_sortie_DNxHD = "" ############################################################################ # identifiant du codec actif # Appel de la classe try: ### Ajout (le 13/08/2010) de self.spec_sortie_DNxHD, self.son_sortie_DNxHD # wfa = WidgetFFmpegAvchd(self.chemins, self.cheminFichierEnregistrerVideo, self.codec_sortie, self.reso_largeur_sortie, self.reso_hauteur_sortie, self.nbreImgSec_sortie, self.qualite_sortie, self.spec_sortie_DNxHD, self.son_sortie_DNxHD, self) wfa.setWindowTitle(_(u"Traitement AVCHD (FFmpeg)")) wfa.exec_() ############################################################################ except: messageErrWfa=QMessageBox(self) messageErrWfa.setText(_(u"Problème lors de la conversion des fichiers (FFmpeg)\n")) messageErrWfa.setWindowTitle(_(u"Erreur")) messageErrWfa.setIcon(QMessageBox.Warning) messageErrWfa.exec_() return def actionsFin(self,sorties) : self.lstFichiersSortie = sorties # Mise à jour de la liste des fichiers de sortie dans la comboBox de sélection. self.mplayer.setVideos(sorties) self.radioConvert.setChecked(True) self.radioSource.setEnabled(True) self.radioSource.setChecked(False) self.radioConvert.setEnabled(True) self.boutCompare.setEnabled(True) self.infoLog(None, self.chemins, None, sorties) def afficherAide(self): "Boîte de dialogue de l'aide du cadre" super( AnimationEncodageAVCHD,self).afficherAide(_(u"<p><b>Vous pouvez ici transcoder des vidéos AVCHD (extension mts ou m2ts) en différents autres codecs tels que MOV, VOB, MPEG2, Motion JPEG.</b></p><p><b>AVCHD (Advanced Video Codec High Definition) est un format d'enregistrement et stockage numérique vidéo haute définition, mis au point par Sony et Panasonic. Ce format permet de réduire la taille des fichiers HD, tout en préservant un certain niveau de qualité de l'image restituée. Plus particulièrement adapté aux caméscopes, ce format vient en complément des formats HDV et MiniDV. <br>Source: http://fr.wikipedia.org/wiki/Advanced_Video_Codec_High_Definition.</b></p><p><b>Il est à noter ici (pour la gestion de l'AVCHD) que vous bénéficiez d'un traitement par lot, c'est à dire que tous les fichiers que vous allez charger seront traités (et pas seulement le fichier sélectionné).</b></p><p>Dans l'onglet <b>'Vidéo(s) source'</b> cliquez sur le bouton <b>Ajouter</b>, une boîte de dialogue apparaît, sur la partie gauche sélectionnez le répertoire (au besoin dépliez les sous-répertoires), allez chercher la/les vidéo(s). Si vous voulez sélectionner plusieurs vidéos d'un coup, maintenez la touche <b>CTRL</b> (ou <b>SHIFT</b>) du clavier enfoncée (tout en sélectionnant vos vidéos), cliquez sur <b>Ajouter</b>.</p><p>Vous pouvez dès lors sélectionner une vidéo dans la liste et la visionner (par le bouton juste à la droite de cette liste), vous noterez que vous pouvez visionner la vidéo en quatre tiers, en seize neuvième ou avec les proportions d'origine de la vidéo (w;h). De même si vous le désirez, vous pouvez obtenir des informations complètes sur la vidéo sélectionnée, et ce par le bouton <b>'Infos'</b> (en bas).</p><p>Dans l'onglet <b>'Réglages'</b> sélectionnez le codec, et s'il le faut, faites les réglages de l'<b>Encodage sortie vidéo</b>, de la <b>Résolution sortie</b>, du <b>Nombre d'images/seconde</b> (n'est pas disponible pour une sortie en VOB, MPEG2 et MPEG1) et la <b>Qualité de la vidéo</b>.</p><p>Une fois tout ceci fait, cliquez sur le bouton <b>'Appliquer'</b>, sélectionnez le répertoire de sauvegarde, indiquez votre <b>'Nom de fichier'</b>, cliquez sur le bouton <b>'Enregistrer'</b> et attendez le temps de la conversion.</p><p>Dans l'onglet <b>'Visionner vidéo'</b> vous pouvez visionner le résultat (avant la conversion) en sélectionnant <b>'vidéo(s) source(s)'</b>, après la conversion <b>'vidéo convertie'</b> ou bien encore les deux en même temps, en cliquant sur le bouton <b>'Comparateur de vidéos'</b>. A ce propos, vous noterez (et comme vous bénéficiez d'un traitement par lot des vidéos) que vous pouvez sélectionner (et ce pour chaque cas) la vidéo à visionner dans une liste déroulante qui est apparue juste en dessous du bouton de lecture. Dans le comparateur de vidéos, la sélection d'une vidéo avant encodage dans la liste déroulante (et ce dans la partie gauche), correspondra exactement à la vidéo après conversion (dans la partie droitre), vous pourrez ainsi comparer, comme il se doit, les deux vidéos.</p><p>L'onglet <b>'Infos'</b> vous permet de voir les vidéos chargées (avec leurs chemins exacts) avant et après conversion.</p>")) def saveFiles(self): ''' # On sauvegarde la liste des fichiers chargés ''' self.afficheurVideoSource.saveFileLocation(self.idSection) def loadFiles(self): ''' # On sauvegarde la liste des fichiers chargés ''' self.afficheurVideoSource.loadFileLocation(self.idSection) def loadOptions(self): """ On charge les différentes variables necessaire au widget """ idCodec = EkdConfig.get(self.idSection,'codec') self.codecSortie.setCurrentIndex(int(idCodec)) resolution = EkdConfig.get(self.idSection,'resolution') self.resoSortie.setCurrentIndex(int(resolution)) frequence = EkdConfig.get(self.idSection,'images_par_seconde') self.nbrImage.setValue(int(frequence)) qualite = EkdConfig.get(self.idSection,'qualite') self.qualite.setValue(int(qualite)) def load(self): ''' Chargement de la configuration de tous les objets ''' self.loadFiles() def save(self): ''' Sauvegarde de la configuration de tous les objets ''' self.saveFiles()
class Animation_MontagVideoVidPlusAudio(Base): """ -------------------------------------------- # Cadre accueillant les widgets de : # Animation >> Montage vidéo >> Vidéo et Audio # -------------------------------------------""" def __init__(self, statusBar, parent=None): vbox=QVBoxLayout() #=== Variable de configuration ===# self.config=EkdConfig # Identifiant de la classe self.idSection = "animation_montage_video_et_audio" super(Animation_MontagVideoVidPlusAudio, self).__init__(boite='vbox', titre=_(u"Montage: Vidéo et audio")) self.printSection() self.repTampon = EkdConfig.getTempDir() + os.sep if os.path.isdir(self.repTampon) is False: os.makedirs(self.repTampon) # Au cas où le répertoire existait déjà et qu'il n'était pas vide -> purge (simple précaution) for toutRepCompo in glob.glob(self.repTampon+'*.*'): os.remove(toutRepCompo) # Liste de fichiers initiaux (contenu dans le fichier de configuration) self.lstFichiersSource = [] self.lstFichiersSourceProv = [] # idem mais provisoire (sert à récupérer les chemins dans l'ordre de sélection) self.lstFichiersSourceAudio = [] self.lstFichiersSourceProvAudio = [] # Par soucis de lisibilité, on préfère ne pas utiliser des objets présent dans le parent de cette façon # mais plutôt le passer en paramètre (le code est plus facile à lire de cette façon) self.statusBar = statusBar #------------------------------------------------------------------- self.afficheurVideoSource=SelectWidget(extensions = ["*.avi", "*.mpg", "*.mpeg", "*.mjpeg", "*.flv", "*.mp4", "*.h264", "*.dv", "*.vob"], mode="texte", video = True) ################################################################################### # Onglets self.indexVideoSource = self.add(self.afficheurVideoSource, _(u'Video(s) source')) self.connect(self.afficheurVideoSource,SIGNAL("fileSelected"),self.getFile) self.connect(self.afficheurVideoSource, SIGNAL("pictureChanged(int)"), self.getFile) #-------------------------------------------------------------------- # ------------------------------------------------------------------- # Boîte de groupe : "Fichiers source" # ------------------------------------------------------------------- extFormat=[] for fmt in parent.soxSuppFormat : extFormat.append("*."+fmt) self.afficheurAudioSource=SelectWidget(extensions=extFormat ,mode="texte", audio = True) # Onglets self.indexAudioSource = self.add(self.afficheurAudioSource, _(u'Audio(s) source')) self.connect(self.afficheurAudioSource,SIGNAL("fileSelected"), self.getFileA) self.connect(self.afficheurAudioSource, SIGNAL("pictureChanged(int)"), self.getFileA) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = ["video","audio"] # Défini le type de fichier source. self.typeSortie = "video" # Défini le type de fichier de sortie. self.sourceEntrees = [self.afficheurVideoSource, self.afficheurAudioSource] # Fait le lien avec le sélecteur de fichier source. self.addReglage("vbox") #=== Widget qui seront inclus dans la boite de réglage ===# self.ordreVideo = selectJoinMultipleSound(0, self) self.ordreVideo.setTitleAndTips(_(u"Fichiers vidéos à joindre"), _(u"Liste des fichiers vidéo à joindre. <b>Pour information, vous pouvez monter et descendre les fichiers grâce aux flèches haut et bas (les fichiers apparaissant en haut de la liste sont ceux qui seront au début du montage)</b>")) self.ordreAudio = selectJoinMultipleSound(0, self) self.ordreAudio.setTitleAndTips(_(u"Fichiers audios à joindre"), _(u"Liste des fichiers audio à joindre. <b>Pour information, vous pouvez monter et descendre les fichiers grâce aux flèches haut et bas (les fichiers apparaissant en haut de la liste sont ceux qui seront au début du montage)</b>")) self.layoutReglage.addWidget(self.ordreVideo) self.layoutReglage.addWidget(self.ordreAudio) # --------------------------- # Boite de groupe de mplayer # --------------------------- self.addPreview() self.addLog() def getFile(self): ''' # On utilise la nouvelle interface de récupération des vidéos Récupération de la vidéo source selectionnée ''' self.chemin = self.afficheurVideoSource.getFile() self.lstFichiersSource = self.afficheurVideoSource.getFiles() self.ordreVideo.addSoundAction(self.lstFichiersSource) self.ordreVideo.delFile(self.lstFichiersSource) self.boutApp.setEnabled(True) self.mplayer.setEnabled(True) self.mplayer.setVideos([self.chemin]) self.radioSource.setChecked(True) self.radioSource.setEnabled(True) self.emit(SIGNAL("loaded")) return self.chemin def getFileA(self): ''' # On utilise la nouvelle interface de récupération des fichiers audio ''' chemin = self.afficheurAudioSource.getFile() self.lstFichiersSourceAudio = self.afficheurAudioSource.getFiles() self.ordreAudio.addSoundAction(self.lstFichiersSourceAudio) self.ordreAudio.delFile(self.lstFichiersSourceAudio) self.boutApp.setEnabled(True) self.mplayer.setEnabled(True) self.mplayer.setVideos([chemin]) self.radioSource.setChecked(True) self.radioSource.setEnabled(True) self.emit(SIGNAL("loaded")) return chemin def fctRadioSource(self, bool=None): """"Communique la vidéo appropriée à mplayer""" if bool: self.mplayer.listeVideos = self.lstFichiersSource try : self.radioConvert.setChecked(False) except : None def fctRadioAudio(self, bool=None): """"Communique la vidéo appropriée à mplayer""" if bool: self.mplayer.listeVideos = self.lstFichiersSourceAudio def fctRadioConvert(self, bool=None): """"Communique la vidéo appropriée à mplayer""" if bool: self.mplayer.listeVideos = self.lstFichiersSortie try : self.radioSource.setChecked(False) except : None def listeChemins(self, ch): """transforme une chaine de caractères en liste de chemins""" lst = ch.split("'") for i in lst: # i: élément de la ligne if (',' in i) or ('[' in i) or (']' in i): lst.remove(i) return lst def ouvrirSourceAudio(self, nomEntree=None): """ Récupération des chemins vidéo sélectionnée """ chemin = self.recupSourcesAudio(nomEntree) if not chemin: return self.lstFichiersSourceAudio = chemin self.lstFichiersSourceProvAudio = self.lstFichiersSourceAudio[:] # ':' car sinon on créé un alias # Ordonner la liste si l'option a été sélectionnée et l'afficher dans la ligne d'édition self.ordonnerListeAudio() self.boutApp.setEnabled(True) self.mplayer.setEnabled(True) self.mplayer.listeVideos = self.lstFichiersSourceAudio self.radioAudio.setChecked(True) self.radioConvert.setEnabled(False) if len(self.lstFichiersSource)!=0: self.radioSource.setEnabled(True) self.radioAudio.setEnabled(True) else: self.radioSource.setEnabled(False) self.radioAudio.setEnabled(False) self.statusBar.showMessage(_(u"La vidéo résultante ne pourra pas être lue avec tous les logiciels")) def ouvrirSourceVideo(self, nomEntree=None): """ Récupération des chemins vidéo sélectionnée """ chemin = self.recupSources(nomEntree) if not chemin: return self.lstFichiersSource = chemin self.lstFichiersSourceProv = self.lstFichiersSource[:] # ':' car sinon on créé un alias # Ordonner la liste si l'option a été sélectionnée et l'afficher dans la ligne d'édition self.ordonnerListeVideo() self.boutApp.setEnabled(True) self.mplayer.setEnabled(True) self.mplayer.listeVideos = self.lstFichiersSource self.radioSource.setChecked(True) self.radioConvert.setEnabled(False) if len(self.lstFichiersSourceAudio)!=0: self.radioSource.setEnabled(True) self.radioAudio.setEnabled(True) else: self.radioSource.setEnabled(False) self.radioAudio.setEnabled(False) self.statusBar.showMessage(_(u"La vidéo résultante ne pourra pas être lue avec tous les logiciels")) def afficherAide(self): """ Boîte de dialogue de l'aide """ super(Animation_MontagVideoVidPlusAudio, self).afficherAide(_(u"<p><b>Sous le terme de montage vidéo, vous pouvez ici assembler des vidéos pour en constituer une seule, mais aussi des fichiers audio. EKD peut assembler des vidéos (mais aussi des fichiers audio) de différentes nature (extensions).</b></p><p><b>Il est à noter ici (pour le Montage vidéo) que vous bénéficiez d'un traitement par lot, c'est à dire que tous les fichiers que vous allez charger seront traités (et pas seulement le fichier sélectionné).</b></p><p>Dans l'onglet <b>'Vidéo(s) source'</b> cliquez sur le bouton <b>Ajouter</b>, une boîte de dialogue apparaît, sur la partie gauche sélectionnez le répertoire (au besoin dépliez les sous-répertoires), allez chercher la/les vidéo(s). Si vous voulez sélectionner plusieurs vidéos d'un coup, maintenez la touche <b>CTRL</b> (ou <b>SHIFT</b>) du clavier enfoncée (tout en sélectionnant vos vidéos), cliquez sur <b>Ajouter</b>.</p><p>Vous pouvez dès lors sélectionner une vidéo dans la liste et la visionner (par le bouton juste à la droite de cette liste), vous noterez que vous pouvez visionner la vidéo en quatre tiers, en seize neuvième ou avec les proportions d'origine de la vidéo (w;h). De même si vous le désirez, vous pouvez obtenir des informations complètes sur la vidéo sélectionnée, et ce par le bouton <b>'Infos'</b> (en bas).</p><p>Passez maintenant dans l'onglet <b>'Audio(s) source'</b> cliquez sur le bouton <b>Ajouter</b>, une boîte de dialogue apparaît, sur la partie gauche sélectionnez le répertoire (au besoin dépliez les sous-répertoires), allez chercher votre/vos fichier(s) audio. Si vous voulez sélectionner plusieurs fichiers audio d'un coup, maintenez la touche <b>CTRL</b> (ou <b>SHIFT</b>) du clavier enfoncée (tout en sélectionnant vos fichiers), cliquez sur <b>Ajouter</b>.</p><p>Vous pouvez dès lors sélectionner un fichier audio dans la liste et l'écouter (par le bouton juste à la droite de cette liste). De même si vous le désirez, vous pouvez obtenir des informations complètes sur le fichier audio sélectionné, et ce par le bouton <b>'Infos'</b> (en bas).</p><p>Dans l'onglet <b>Réglages</b>, vous pouvez changer l'ordre de montage des fichiers et ce en remontant ou en redescendant (par les flèches haut et bas) dans les listes <b>'Fichiers vidéo à joindre'</b> et <b>'Fichiers audio à joindre'</b>.</p><p>Une fois tout ceci fait, cliquez sur le bouton <b>'Appliquer'</b>, sélectionnez le répertoire de sauvegarde, indiquez votre <b>'Nom de fichier'</b>, cliquez sur le bouton <b>'Enregistrer'</b> et attendez le temps de la conversion.</p><p>Dans l'onglet <b>'Visionner vidéo'</b> vous pouvez visionner le résultat (avant la concaténation) en sélectionnant <b>'vidéo(s) source(s)'</b>, après la concaténation <b>'vidéo convertie'</b>.</p><p>L'onglet <b>'Infos'</b> vous permet de voir les vidéos et fichiers audio chargés (avec leurs chemins exacts) avant et après conversion.</p>")) def stat_dim_video(self): """Calcul statistique des dimensions des vidéos les plus présentes dans le lot""" from gui_modules_animation.infoVideo import infovideo listePrepaRedim = [] # Détection des dimensions différentes (résolutions) # dans les vidéos chargées par l'utilisateur for parcVideoResolution in self.lstFichiersSource: info = infovideo(parcVideoResolution) listePrepaRedim.append((info.videoLargeur, info.videoHauteur)) # Merci beaucoup à Marc Keller de la liste: python at aful.org de m'avoir # aidé pour cette partie (les 4 lignes en dessous) dictSeq={}.fromkeys(listePrepaRedim, 0) for cle in listePrepaRedim: dictSeq[cle]+=1 self.lStatDimSeq=sorted(zip(dictSeq.itervalues(), dictSeq.iterkeys()), reverse=1) #print self.lStatDimSeq[0][1] EkdPrint(u"%s" % str(self.lStatDimSeq[0][1])) self.dimStatVideo=self.lStatDimSeq[0][1] ''' print print "Toutes les dimensions des vidéos (avec le nbre de vidéos):", self.lStatDimSeq print 'Dimension des vidéos la plus presente dans la sequence:', self.dimStatVideo print "Nombre de tailles de vidéos différentes dans le lot :", len(self.lStatDimSeq) print ''' EkdPrint(u'') EkdPrint(u"Toutes les dimensions des vidéos (avec le nbre de vidéos): %s" % self.lStatDimSeq) EkdPrint(u'Dimension des vidéos la plus presente dans la sequence: %s' % str(self.dimStatVideo)) EkdPrint(u"Nombre de tailles de vidéos différentes dans le lot: %s" % str(len(self.lStatDimSeq))) EkdPrint(u'') if len(self.lStatDimSeq)>1: return 0 else: return 1 ######################################################################################### def recupOrdreAV(self) : self.lstFichiersSource = self.ordreVideo.getListFile() self.lstFichiersSourceAudio = self.ordreAudio.getListFile() def appliquer(self, nomSortie=None, ouvert=1): """Fusion de vidéos""" self.recupOrdreAV() # ----- Vidéo ------ # self.stat_dim_video() resolution = self.dimStatVideo # Chemin du répertoire temporaire rep_video_ext_resol = self.repTampon + 'video_extension_resol' + os.sep # Scan des extensions des vidéos chargées pour concaténation des vidéos lVideoExt = [parcVidExt.split('.') for parcVidExt in self.lstFichiersSource] lExtVideo = [parcExtVideo[1].lower() for parcExtVideo in lVideoExt] # On elimine les doublons s'il y en a. TTes les # extensions différentes sont mises en avant uniqExtVideo = list(set(lExtVideo)) # ----- Audio ------ # # Chemin du répertoire temporaire rep_video_audio = self.repTampon + 'concat_audio' + os.sep # Scan des extensions des fichiers audio chargés pour concaténation lAudioExt = [parcAudExt.split('.') for parcAudExt in self.lstFichiersSourceAudio] lExtAudio = [parcExtAudio[1].lower() for parcExtAudio in lAudioExt] # On elimine les doublons s'il y en a. TTes les # extensions différentes sont mises en avant uniqExtAudio = list(set(lExtAudio)) ####################################################################### #=== Récupération du fichier de sortie ===# chemin=unicode(self.ordreVideo.getListFile()) if not nomSortie: # suffix du fichier actif #suffix=os.path.splitext(chemin)[1] suffix = u".avi" ############################################################################## # Si la liste contient plus d'un élément, c'est à dire si elle contient des # extensions différentes et si la résolution des vidéos est différente. # ---------------------------------------------------------------------------- # Ici l'extension à la sortie sera en avi car on encode en Motion JPEG. if len(uniqExtVideo) > 1 or len(self.lStatDimSeq) > 1: suffix = '.avi' ############################################################################## saveDialog = EkdSaveDialog(self, mode="video", suffix=suffix, title=_(u"Sauver")) self.cheminFichierEnregistrerVideo = saveDialog.getFile() #################################################################################################################### else: # module séquentiel self.cheminFichierEnregistrerVideo=nomSortie if not self.cheminFichierEnregistrerVideo: return # Video # Chemin de sortie temporaire de la vidéo self.cheminVideoProv = self.repTampon+u"video" # Audio # Chemin de sortie temporaire de l'audio self.cheminAudioProv = self.repTampon+u"audio.wav" if os.path.isdir(rep_video_ext_resol) is False: os.makedirs(rep_video_ext_resol) #from moteur_modules_animation.mencoder_concat_video import WidgetMEncoderConcatExtResol from gui_modules_common.mencoder_concat_gui import WidgetMEncoderConcatExtResol self.sourceVideo = self.lstFichiersSource # Si la liste contient plus d'un élément, c'est à dire si elle contient des # extensions différentes et si la résolution des vidéos est différente if len(uniqExtVideo) > 1 or len(self.lStatDimSeq) > 1: wmcer = WidgetMEncoderConcatExtResol(self.lstFichiersSource, valeurNum=resolution) wmcer.setWindowTitle(_(u"Traitement vidéo (extension et résolution)")) wmcer.exec_() # Les vidéos chargées en vue de la concaténation sont maintenant ds # le rep tampon .../video_extension_resol self.lstFichiersSource = glob.glob(unicode(rep_video_ext_resol+'*.*')) if debug : #print self.lstFichiersSource EkdPrint(u"%s" % self.lstFichiersSource) # Mise en ordre (numérotation) car cela peut # poser problème au moment de la concaténation self.lstFichiersSource.sort() if debug : #print "sources tmp mise en ordre : ", self.lstFichiersSource EkdPrint(u"sources tmp mise en ordre : %s" % self.lstFichiersSource) # Comme l'encodage se fait ici en Motion JPEG # (AVI) l'extension doit être .avi suffix = '.avi' else: # Au cas où le répertoire existait déjà et qu'il n'était pas vide -> purge # ... ici dans le cas de vidéos avec la même extension et la même résolution if os.path.isdir(rep_video_ext_resol) is True: for toutRepCompoVideo in glob.glob(rep_video_ext_resol+'*.*'): os.remove(toutRepCompoVideo) ################################################################################################## self.sourceAudio = self.lstFichiersSourceAudio self.process = soxProcessMulti(self.lstFichiersSourceAudio, self.cheminAudioProv, u"") self.process.show() self.process.run() self.connect(self.process,SIGNAL("endProcess"),self.endProcess) def endProcess(self, sortie) : # Suite du process self.process.close() # Les fichiers audio chargés en vue de la concaténation # sont maintenant ds le rep tampon .../concat_audio ################################################################################################### ### Concaténation élégante (à transformer en objet dans le package moteur) ############### # on écrit dans "output" (descripteur de fichier de cheminVideoProv) try: output = open(self.cheminVideoProv, 'wb+') for cheminFichier in self.lstFichiersSource: input = open(cheminFichier, 'rb') output.write(input.read()) input.close() output.close() except Exception, details: #print "**** DEBUG: Erreur dans l'ouverture du fichier : " , details EkdPrint(u"**** DEBUG: Erreur dans l'ouverture du fichier : %s" % details) # Maintenant cheminVideoProv contient la concaténation de tous fichiers ################################################################################################### #=== Commandes de concaténation finale ===# if debug : #print self.cheminVideoProv, self.cheminAudioProv, self.cheminFichierEnregistrerVideo EkdPrint(u"%s %s %s" % (self.cheminVideoProv, self.cheminAudioProv, self.cheminFichierEnregistrerVideo)) try: mencoder = WidgetMEncoder('fusion_audio_et_video_2', (self.cheminVideoProv, self.cheminAudioProv), self.cheminFichierEnregistrerVideo, laisserOuvert=1) mencoder.setWindowTitle(_(u"Fusion des fichiers vidéos et audios")) mencoder.exec_() except: messageErrAnEnc=QMessageBox(self) messageErrAnEnc.setText(_(u"Problème lors de l'étape de concaténation vidéo et audio (mencoder)")) messageErrAnEnc.setWindowTitle(_(u"Erreur")) messageErrAnEnc.setIcon(QMessageBox.Warning) messageErrAnEnc.exec_() os.remove(self.cheminVideoProv) os.remove(self.cheminAudioProv) return # Suppression des fichiers temporaires os.remove(self.cheminVideoProv) os.remove(self.cheminAudioProv) self.lstFichiersSortie = self.cheminFichierEnregistrerVideo # pour la boite de dialogue de comparaison self.radioConvert.setEnabled(True) self.radioConvert.setChecked(True) self.radioSource.setEnabled(True) self.radioSource.setChecked(False) #self.radioAudio.setEnabled(True) self.infoLog(None, self.sourceVideo, self.sourceAudio, self.lstFichiersSortie) return self.lstFichiersSortie # module séquentiel
class Animation_ReglagesDivers(Base): # ---------------------------------------------------------------------------------------- # Cadre accueillant les widgets de : Animation >> Séparer le flux vidéo et le flux audio # ---------------------------------------------------------------------------------------- def __init__(self): # ------------------------------- # Parametres généraux du widget # ------------------------------- vbox=QVBoxLayout() #=== Variable de configuration ===# self.config=EkdConfig #=== Identifiant de la classe ===# self.idSection = "animation_reglages_divers" super(Animation_ReglagesDivers, self).__init__('hbox', titre=_(u"Nombre d'images par seconde")) self.printSection() # ------------------------------------------------------------------- # Boîte de groupe : "Fichier vidéo source" # ------------------------------------------------------------------- self.afficheurVideoSource=SelectWidget(extensions = ["*.avi", "*.dv"], mode="texte", video = True) # Onglets self.indexVideoSource = self.add(self.afficheurVideoSource, _(u'Video(s) source')) self.connect(self.afficheurVideoSource,SIGNAL("fileSelected"),self.getFile) self.connect(self.afficheurVideoSource, SIGNAL("pictureChanged(int)"), self.getFile) #-------------------------------------------------------------------- self.lstFichiersSortie = [] ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "video" # Défini le type de fichier source. self.typeSortie = "video" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurVideoSource # Fait le lien avec le sélecteur de fichier source. # ------------------------------------------------------------------- # Boîte de groupe "Réglage de sortie de l'encodage" # ------------------------------------------------------------------- groupReglage = QGroupBox() self.layoutReglage = QHBoxLayout(groupReglage) ##=== Widget qui seront inclus dans la boite de réglage ===# # boite de spin self.spin=QSpinBox() # self car on va récupérer la variable depuis le moteur self.spin.setRange(1,60) # valeur 22 par défaut self.connect(self.spin,SIGNAL("valueChanged(int)"),self.spinChange) # curseur self.curseur = QSlider(Qt.Horizontal) self.curseur.setRange(1,60) self.connect(self.curseur, SIGNAL("sliderMoved(int)"), self.curseurChange) #=== Chargement du paramètre de configuration ===# try: self.spin.setValue(int(self.config.get(self.idSection,'spin'))) except: self.spin.setValue(22) # info txt2 = _(u"La vidéo source contient") txt3=_(u"img/s") self.info=QLabel("%s x %s" %(txt2,txt3)) self.layoutReglage.addWidget(self.spin) self.layoutReglage.addWidget(self.curseur) self.layoutReglage.addWidget(self.info) self.add(groupReglage, _(u"Réglages")) self.addPreview() self.addLog() def getOutputFiles(self, typeSortie) : """Fonction standard pour faire le lien avec le tampon EKD pour récupérer les fichier de sortie du type typeSortie""" if typeSortie == "video" : return self.lstFichiersSortie def getFile(self): ''' # On utilise la nouvelle interface de récupération des vidéos Récupération de la vidéo source selectionnée ''' self.chemin = self.afficheurVideoSource.getFile() self.boutApp.setEnabled(True) self.mplayer.setEnabled(True) self.mplayer.setVideos([self.chemin]) self.emit(SIGNAL("loaded")) return self.chemin def spinChange(self,i): """conserver le spin dans le fichier de configuration et modifie le curseur""" #print "nbr img/s :", i EkdPrint(u"nbr img/s : %d" % i) # sauver spin self.config.set(self.idSection,'spin',i) # sauver curseur self.curseur.setValue(i) def curseurChange(self, i): """Quand on change la position du curseur la valeur du spin est automatiquement modifiée""" self.spin.setValue(i) def fctRadioSource(self, bool=None): """"Communique la vidéo appropriée à mplayer""" if bool: self.mplayer.listeVideos = [[self.chemin]] def fctRadioConvert(self, bool=None): """"Communique la vidéo appropriée à mplayer""" if bool: self.mplayer.listeVideos = self.lstFichiersSortie def nbrImgSecIni(self): """ Calcule et affiche le nombre d'images par seconde de la vidéo source """ chemin = self.getFile() fps = 0 if chemin: fps = str(int(getVideoFPS(chemin)[0])) if fps: txt1 = _(u"La vidéo source contient") txt2=_(u"img/s") self.info.setText(u"%s %s %s" %(txt1,fps,txt2)) #################################################################################### def ouvrirSource(self, nomEntree=None): """Récupération du chemin de la vidéo sélectionnée et activation de certains widgets""" chemin = self.getFile() if not chemin: return self.boutApp.setEnabled(True) self.mplayer.setEnabled(True) self.mplayer.listeVideos = [chemin] self.radioSource.setChecked(True) self.radioSource.setEnabled(False) self.radioConvert.setEnabled(False) self.boutCompare.setEnabled(False) self.nbrImgSecIni() def getFile(self): ''' # On utilise la nouvelle interface de récupération des vidéos Récupération de la vidéo source selectionnée ''' self.chemin = self.afficheurVideoSource.getFile() self.boutApp.setEnabled(True) self.mplayer.setEnabled(True) self.mplayer.setVideos([self.chemin]) self.radioSource.setChecked(True) self.radioSource.setEnabled(True) self.boutApp.setEnabled(True) if self.idSection == "animation_filtresvideo": self.boutApercu.setEnabled(True) self.filtreDecouper.setButtonEnabled(True) # On emet un signal quand le fichier est chargé self.emit(SIGNAL("loaded")) return self.chemin ################################################################################### def afficherAide(self): """ Boîte de dialogue de l'aide """ super(Animation_ReglagesDivers,self).afficherAide(_(u"<p><b>Vous pouvez ici changer le nombre d'images par seconde dans une vidéo.</b></p><p>Dans l'onglet <b>'Vidéo(s) source'</b> cliquez sur le bouton <b>Ajouter</b>, une boîte de dialogue apparaît, sur la partie gauche sélectionnez le répertoire (au besoin dépliez les sous-répertoires), allez chercher la/les vidéo(s). Si vous voulez sélectionner plusieurs vidéos d'un coup, maintenez la touche <b>CTRL</b> (ou <b>SHIFT</b>) du clavier enfoncée (tout en sélectionnant vos vidéos), cliquez sur <b>Ajouter</b>.</p><p>Vous pouvez dès lors sélectionner une vidéo dans la liste et la visionner (par le bouton juste à la droite de cette liste), vous noterez que vous pouvez visionner la vidéo en quatre tiers, en seize neuvième ou avec les proportions d'origine de la vidéo (w;h). De même si vous le désirez, vous pouvez obtenir des informations complètes sur la vidéo sélectionnée, et ce par le bouton <b>'Infos'</b> (en bas).</p><p>Dans l'onglet <b>'Réglages'</b> réglez le nombre d'images par seconde.</p><p>Une fois tout ceci fait, cliquez sur le bouton <b>'Appliquer'</b>, sélectionnez le répertoire de sauvegarde, indiquez votre <b>'Nom de fichier'</b>, cliquez sur le bouton <b>'Enregistrer'</b> et attendez le temps de la conversion. A la fin cliquez sur le bouton <b>'Voir les informations d'encodage'</b> et fermez cette dernière fenêtre après avoir vu les informations en question.</p><p>Dans l'onglet <b>'Visionner vidéo'</b> vous pouvez visionner le résultat (avant la conversion) en sélectionnant <b>'vidéo(s) source(s)'</b>, après la conversion <b>'vidéo convertie'</b> ou bien encore les deux en même temps, en cliquant sur le bouton <b>'Comparateur de vidéos'</b>.</p><p>L'onglet <b>'Infos'</b> vous permet de voir les vidéos chargées (avec leurs chemins exacts) avant et après conversion.</p>")) def appliquer(self, nomSortie=None, ouvert=1): """ appelle la boite de dialogue de sélection de fichier à sauver et appel de la fonction de changement du nombre d'images par seconde """ #=== Détermination des chemins d'entrée et sortie ===# chemin=unicode(self.getFile()) if not nomSortie: # suffix du fichier actif suffix=os.path.splitext(chemin)[1] saveDialog = EkdSaveDialog(self, mode="video", suffix=suffix, title=_(u"Sauver")) cheminFichierEnregistrerVideo = saveDialog.getFile() else: # module séquentiel cheminFichierEnregistrerVideo = nomSortie if not cheminFichierEnregistrerVideo: return ################################################################################### nbrImgSec = str(self.spin.value()) try: #### le changement de framerate est maintenant géré par FFmpeg ##### ffmpeg = WidgetFFmpeg('idx', chemin, cheminFichierEnregistrerVideo, valeurNum=nbrImgSec, laisserOuvert=ouvert) ffmpeg.setWindowTitle(_(u"Réglage divers")) ffmpeg.exec_() ########################################################################################### except: messageErrAnEnc=QMessageBox(self) messageErrAnEnc.setText(_(u"Problème lors du changement du nombre d'images par seconde (mencoder)")) messageErrAnEnc.setWindowTitle(_(u"Erreur")) messageErrAnEnc.setIcon(QMessageBox.Warning) messageErrAnEnc.exec_() return self.lstFichiersSortie = cheminFichierEnregistrerVideo # chemin de la vidéo convertie pour le 2ème mplayer self.radioSource.setEnabled(True) self.radioConvert.setEnabled(True) self.radioSource.setChecked(False) self.radioConvert.setChecked(True) self.boutCompare.setEnabled(True) self.infoLog(None, chemin, None, cheminFichierEnregistrerVideo) return self.lstFichiersSortie # module séquentiel def sequentiel(self, entree, sortie, ouvert=0): """Utile dans le module du même nom. Applique les opérations de la classe. Retourne le vrai nom du fichier de sortie""" self.ouvrirSource(entree) return self.appliquer(sortie, ouvert) def sequentielReglage(self): """Utile dans le module du même nom. Récupère le widget de réglage associé à l'identifiant donné en 1er argument. Retourne l'instance du widget de réglage""" return self.groupReglage def saveFiles(self): ''' # On sauvegarde la liste des fichiers chargés ''' self.afficheurVideoSource.saveFileLocation(self.idSection) def loadFiles(self): ''' # On sauvegarde la liste des fichiers chargés ''' self.afficheurVideoSource.loadFileLocation(self.idSection) def load(self): ''' Chargement de la configuration de tous les objets ''' self.loadFiles() def save(self): ''' Sauvegarde de la configuration de tous les objets ''' self.saveFiles()
class MusiqueSon_normalize(Base): # ----------------------------------- # Cadre accueillant les widgets de : # Musique-Son >> Encodage # ----------------------------------- def __init__(self, parent): # ------------------------------- # Parametres généraux du widget # ------------------------------- self.config=EkdConfig self.parent = parent #=== Identifiant de la classe ===# self.idSection = "son_normaliser_convertir_musique_ou_son" super(MusiqueSon_normalize, self).__init__(None, None, None, 'vbox') # Base module de animation self.setTitle(_(u"Normaliser et convertir un fichier audio")) self.printSection() #------------------------------------------------------------------------ # TabWidget pour les réglages et pour l'écoute du résultat #------------------------------------------------------------------------ extFormat=[] for fmt in self.parent.soxSuppFormat : extFormat.append("*."+fmt) # Widget standard de sélection de fichier audio dans le tab standard self.selectionAudioFile = SelectWidget(extensions = extFormat, mode="texte", audio = True) # Onglets self.tab.insertTab(0,self.selectionAudioFile, _(u'Son(s) source')) self.connect(self.selectionAudioFile,SIGNAL("fileSelected"),self.synchroFiles) self.connect(self.selectionAudioFile, SIGNAL("pictureChanged(int)"), self.synchroFiles) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "audio" # Défini le type de fichier source. self.typeSortie = "audio" # Défini le type de fichier de sortie. self.sourceEntrees = self.selectionAudioFile # Fait le lien avec le sélecteur de fichier source. # ------------------------------------------------------------------- # Sélection et affichage des fichiers à joindre : "Fichiers audio source" # ------------------------------------------------------------------- reglage = QWidget() regVLayout = QVBoxLayout(reglage) self.selectionFile = choixSonSortieWidget(self.parent.soxSuppFormat, parent=self) regVLayout.addWidget(self.selectionFile) niveau = QGroupBox(_(u"Choix du niveau")) self.choix1 = QRadioButton(_(u"- 3 dB")) self.choix2 = QRadioButton(_(u"- 6 dB")) self.choix3 = QRadioButton(_(u"- 9 dB")) self.choix1.setChecked(True) nivLayout = QHBoxLayout() nivLayout.addWidget(self.choix1) nivLayout.addWidget(self.choix2) nivLayout.addWidget(self.choix3) niveau.setLayout(nivLayout) regVLayout.addWidget(niveau) mode = QGroupBox(_(u"Choix du mode de normalisation")) self.choixm1 = QRadioButton(_(u"Standard")) self.choixm2 = QRadioButton(_(u"Individuel")) self.choixm3 = QRadioButton(_(u"Balancé")) self.choixm1.setChecked(True) modeLayout = QHBoxLayout() modeLayout.addWidget(self.choixm1) modeLayout.addWidget(self.choixm2) modeLayout.addWidget(self.choixm3) mode.setLayout(modeLayout) regVLayout.addWidget(mode) regVLayout.addStretch() self.tab.addTab(reglage,_(u"Réglage")) #------------------------------------------------------------------------ # Ecoute du résultat #------------------------------------------------------------------------ #=== Widgets mplayer ===# widget = QWidget() vboxMplayer = QVBoxLayout(widget) vboxMplayer.addStretch() hbox = QHBoxLayout() vboxMplayer.addLayout(hbox) hbox.addStretch() self.mplayer=Mplayer(taille=(300,270), facteurLimitant=Mplayer.LARGEUR, choixWidget=(Mplayer.PAS_PRECEDENT_SUIVANT,Mplayer.CURSEUR_A_PART)) self.mplayer.setAudio(True) hbox.addWidget(self.mplayer) hbox.addStretch() vboxMplayer.addStretch() self.mplayer.setEnabled(False) self.tab.addTab(widget, _(u"Son créé")) ### pour les infos supplémentaires self.addLog() # ------------------------------------------------------------------- # widgets du bas : ligne + boutons Aide et Appliquer # ------------------------------------------------------------------- def synchroFiles(self): if len(self.selectionAudioFile.getFiles()) > 0 : self.boutApp.setEnabled(True) else : self.boutApp.setEnabled(False) def endProcess(self, sortie) : self.mplayer.setEnabled(True) self.mplayer.listeVideos = [sortie] self.tab.setCurrentIndex(1) self.infoLog(None, None, self.selectionAudioFile.getFile(), sortie) def afficherAide(self): """ Boîte de dialogue de l'aide """ self.aide = EkdAide(550,400,self) self.aide.setText(_(u"<p><b>Ici vous pouvez normaliser des fichiers audio, c'est à dire ajuster leur volume sonore. Voici une définition selon Wikipédia de la normalisation audio:<br>http://fr.wikipedia.org/wiki/Normalisation_audio</b></p><p>Dans l'onglet <b>Son(s) source</b> cliquez sur le bouton <b>Ajouter</b>, une boîte de dialogue apparaît, sur la partie gauche sélectionnez le répertoire (au besoin dépliez les sous-répertoires), allez chercher la/les fichiers(s) audio. Si vous voulez sélectionner plusieurs fichiers audio d'un coup, maintenez la touche <b>CTRL</b> (ou <b>SHIFT</b>) du clavier enfoncée (tout en sélectionnant vos fichiers audio), cliquez sur <b>Ajouter</b>.</p><p>Vous pouvez dès lors sélectionner un fichier audio dans la liste et le lire (par le bouton juste à la droite de cette liste). De même si vous le désirez, vous pouvez obtenir des informations complètes sur le fichier audio sélectionné, et ce par le bouton <b>Infos</b> (en bas).</p><p>Dans l'onglet <b>Réglages</b> sélectionnez le <b>Format du fichier de sortie</b>, et s'il le faut <b>Réglage expert</b> et faites éventuellement le réglage de la <b>Qualité d'encodage</b>, puis réglez le <b>Choix du niveau</b> et le <b>Choix du mode de normalisation</b>.</p><p>Une fois tout ceci fait, cliquez sur le bouton <b>Appliquer</b>, sélectionnez le répertoire de sauvegarde, indiquez votre <b>Nom de fichier</b>, cliquez sur le bouton <b>Enregistrer</b> et attendez le temps de la conversion. A la fin du traitement cliquez sur le bouton <b>Fermer</b> de la fenêtre <b>Affichage de la progression</b>.</p><p>Dans l'onglet <b>Son créé</b> vous pouvez lire le résultat.</p><p>L'onglet <b>Infos</b> vous permet de voir les fichiers audio chargés (avec leurs chemins exacts) avant et après conversion.</p>")) self.aide.show() def appliquer(self): """ appelle la boite de dialogue de sélection de fichier à sauver """ suffixSortie = u"."+self.selectionFile.getFileExt() saveDialog = EkdSaveDialog(self, mode="audio", suffix=suffixSortie, title=_(u"Sauver")) cheminAudioSorti = saveDialog.getFile() if not cheminAudioSorti: return # récupération du chemin des fichiers audio source cheminAudioSource=self.selectionAudioFile.getFile() # suffix du fichier actif suffix=os.path.splitext(cheminAudioSource)[1] if suffix == u".mp3" or suffix == u".MP3" : chemin = u"-t mp3 \""+cheminAudioSource+u"\"" elif suffix == u".ogg" or suffix == u".OGG" : chemin = u"-t ogg \""+cheminAudioSource+u"\"" elif suffix == u".mp2" or suffix == u".MP2" : chemin = u"-t mp2 \""+cheminAudioSource+u"\"" elif suffix == u".flac" or suffix == u".FLAC" : chemin = u"-t flac \""+cheminAudioSource+u"\"" else : chemin = u"\""+cheminAudioSource+u"\"" # Définition des options if self.choix1.isChecked() : db = " -3 " if self.choix2.isChecked() : db = " -6 " if self.choix3.isChecked() : db = " -9 " if self.choixm1.isChecked() : mde = "" if self.choixm2.isChecked() : mde = "-i" if self.choixm3.isChecked() : mde = "-b" option = "norm "+mde+db if self.selectionFile.reglageExp.getExpertState() : regExp = self.selectionFile.reglageExp.getC() else : regExp = u"" # Encodage self.process = SoxProcess(chemin, cheminAudioSorti, 1, u"", u""+regExp, option, self.parent) self.process.setSignal(u"Input",u"In:") self.process.show() self.process.run() self.connect(self.process,SIGNAL("endProcess"),self.endProcess) def load(self) : self.selectionAudioFile.loadFileLocation(self.idSection) self.selectionFile.reglageExp.loadConfig(self.idSection) self.choix1.setChecked(int(EkdConfig.get(self.idSection, 'choix1'))) self.choix2.setChecked(int(EkdConfig.get(self.idSection, 'choix2'))) self.choix3.setChecked(int(EkdConfig.get(self.idSection, 'choix3'))) self.choixm1.setChecked(int(EkdConfig.get(self.idSection, 'choixm1'))) self.choixm2.setChecked(int(EkdConfig.get(self.idSection, 'choixm2'))) self.choixm3.setChecked(int(EkdConfig.get(self.idSection, 'choixm3'))) def save(self) : self.selectionAudioFile.saveFileLocation(self.idSection) self.selectionFile.reglageExp.saveConfig(self.idSection) EkdConfig.set(self.idSection, u'choix1', unicode(int(self.choix1.isChecked()))) EkdConfig.set(self.idSection, u'choix2', unicode(int(self.choix2.isChecked()))) EkdConfig.set(self.idSection, u'choix3', unicode(int(self.choix3.isChecked()))) EkdConfig.set(self.idSection, u'choixm1', unicode(int(self.choixm1.isChecked()))) EkdConfig.set(self.idSection, u'choixm2', unicode(int(self.choixm2.isChecked()))) EkdConfig.set(self.idSection, u'choixm3', unicode(int(self.choixm3.isChecked())))
class Base_EncodageFiltre(Base): # ------------------------------------------------------------------- # Cadre accueillant les widgets de : Animation >> Encodage # ------------------------------------------------------------------- def __init__(self, titre=_(u"Titre")): # ------------------------------- # Parametres généraux du widget # ------------------------------- self.printSection() # tout sera mis dans une boîte verticale self.vbox=QVBoxLayout() super(Base_EncodageFiltre, self).__init__(boite='vbox', titre=titre) ## ------------------------------------------------------------------- ## on utilise le selecteur d'image pour les vidéos from gui_modules_image.selectWidget import SelectWidget # Là où on naviguera entre les fichiers self.afficheurVideoSource=SelectWidget(mode="texte", video = True) # Onglets self.indexVideoSource = self.add(self.afficheurVideoSource, _(u'Video(s) source')) self.connect(self.afficheurVideoSource,SIGNAL("fileSelected"),self.getFile) self.connect(self.afficheurVideoSource, SIGNAL("pictureChanged(int)"), self.getFile) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "video" # Défini le type de fichier source. self.typeSortie = "video" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurVideoSource # Fait le lien avec le sélecteur de fichier source. #=== Widget qui seront inclus dans la boite de réglage ===# # Boite de combo self.combo=QComboBox() # Le self est nécessaire pour récupérer l'identifiant # de l'entrée de la boite de combo # Insertion des codecs de compression dans la combo box # Rq: listeCombo est de la forme: [(clé QVariant, texte à afficher,...), ...] for i in self.listeCombo: # self obligatoire pour le module séquentiel self.combo.addItem(i[1], QVariant(i[0])) self.connect(self.combo,SIGNAL("currentIndexChanged(int)"),self.changerItemQStacked) # Utilisation de EkdConfig : modification des sections if self.idSection == "animation_filtresvideo": # On donne la référence de la boite de combo au widget de découpage # pour que ce dernier connaisse le type de découpage sélectionné (assisté ou libre) self.filtreDecouper.setComboParent(self.combo) # Widgets intégrés à la boîte de groupe "Réglage de sortie de l'encodage". # La boîte de combo définie ci-dessus 'sélectionne' ceux qui vont s'afficher # -> utilisation d'un stacked BoxReglage = QGroupBox() layoutReglage = QVBoxLayout(BoxReglage) layoutReglage.addWidget(self.combo) layoutReglage.addWidget(self.stacked) ### self.loadOptions() self.add(BoxReglage, _(u"Réglages")) self.addPreview() self.addLog() # ------------------------------------------------------------------- # widgets du bas : ligne + boutons Aide et Appliquer # ------------------------------------------------------------------- def chargerItemCombo(self, idCombo): """Charger un item particulier de la boite de combo""" try: indice = 0 # indice de la ligne de listeCombo correspondant au type d'ordre for i in self.listeCombo: if i[0]!=idCombo: indice += 1 else: break self.combo.setCurrentIndex(indice) except: self.combo.setCurrentIndex(0) def changerItemQStacked(self, i): """ L'entrée sélectionnée de la boîte de combo modifie le QWidget de réglage du codec associée """ #print "index", i EkdPrint(u"index %d" % i) idCodec=str(self.combo.itemData(i).toString()) #print "idCodec:", idCodec, type(idCodec) EkdPrint(u"idCodec: %s %s" % (idCodec ,type(idCodec))) for k in self.listeCombo: if k[0]==idCodec: # Utilisation de EkdConfig : modification des sections if self.idSection == "animation_filtresvideo": if k[0] in ('decoupageassiste', 'decoupagelibre'): self.filtreDecouper.setStacked(k[0]) if self.stacked: self.stacked.setCurrentIndex(k[2]) EkdConfig.set(self.idSection,'codec', idCodec) def changerTypeDecoupe(self, mode): "Change le mode de découpe vidéo (assisté ou libre)" indexCombo = self.combo.findData(QVariant(mode)) self.combo.setCurrentIndex(indexCombo) def getFile(self): ''' Récupération de la vidéo source selectionnée ''' self.chemin = self.afficheurVideoSource.getFile() self.boutApp.setEnabled(True) self.mplayer.setEnabled(True) self.mplayer.setVideos([self.chemin]) self.radioSource.setChecked(True) self.radioSource.setEnabled(True) #self.radioConvert.setEnabled(True) #self.boutCompare.setEnabled(True) self.boutApp.setEnabled(True) if self.idSection == "animation_filtresvideo": self.boutApercu.setEnabled(True) self.filtreDecouper.setButtonEnabled(True) # On emmet un signal quand le fichier est chargé self.emit(SIGNAL("loaded")) return self.chemin def saveFiles(self): ''' # On sauvegarde la liste des fichiers chargés ''' self.afficheurVideoSource.saveFileLocation(self.idSection) def loadFiles(self): ''' # On sauvegarde la liste des fichiers chargés ''' self.afficheurVideoSource.loadFileLocation(self.idSection) def loadOptions(self): ''' # On charge les options ''' idCodec = EkdConfig.get(self.idSection,'codec') self.chargerItemCombo(idCodec) def load(self): ''' Chargement de la configuration de tous les objets ''' self.loadFiles() self.loadOptions() def save(self): ''' Sauvegarde de la configuration de tous les objets ''' self.saveFiles() def sequentiel(self, entree, sortie, clef=None, ouvert=0): """Utile dans le module du même nom. Applique les opérations associées à l'identifiant donné en 3ème argument. Retourne le vrai nom du fichier de sortie""" self.chargerItemCombo(clef) self.ouvrirSource(entree) return self.appliquer(sortie, ouvert) def sequentielReglage(self, clef): """Utile dans le module du même nom. Récupère le widget de réglage associé à l'identifiant donné en 1er argument. Retourne l'instance du widget de réglage (avec quelques modifications)""" for k in self.listeCombo: if k[0]==clef: self.stacked.setCurrentIndex(k[2]) titrePartielReglage = k[1] self.combo.hide() txt = _(u"Réglage") self.groupReglage.setTitle(txt+' : '+titrePartielReglage) return self.groupReglage
class Image_Divers_Info(QWidget): # ----------------------------------- # Cadre accueillant les widgets de : # Image >> Divers >> Information # ----------------------------------- def __init__(self, geometry): QWidget.__init__(self) # Boite d'alignement vertical vbox=QVBoxLayout(self) # Est-ce que l'image a déjà été affichée une fois ? # -> gain de temps au niveau de l'affichage self.drapeauImage = 0 # 0: non ; 1: oui # Fonctions communes à plusieurs cadres du module Image self.base = Base() # Paramètres de configuration self.config = EkdConfig self.idSection = "image_divers_infos" # Log du terminal self.base.printSection(self.idSection) # Fonction appelant la fenêtre principale self.mainWindowFrameGeometry = geometry self.chemin = [] # Là où s'afficheront les images self.afficheurImgSource=SelectWidget(geometrie = self.mainWindowFrameGeometry) # ----------- # Onglets # ----------- # Onglets self.tab = QTabWidget() #== onglet chargement d'images ===# self.indexTabImgSource = self.tab.addTab(self.afficheurImgSource, _(u'Image(s) source')) #== onglet d'information textuelle ===# self.zoneTexte = QTextEdit("") if PYQT_VERSION_STR < "4.1.0": self.zoneTexte.setText = self.zoneTexte.setPlainText self.zoneTexte.setReadOnly(True) frame = QFrame() hboxTab = QHBoxLayout(frame) hboxTab.addWidget(self.zoneTexte) self.tabIndexText = self.tab.addTab(frame, _("Infos Texte")) #== onglet d'information exif ===# self.table = QTableWidget() self.tabIndexExif = self.tab.addTab(self.table, _("Infos Exif")) vbox.addWidget(self.tab) # ------------------------------------------------------------------- # widgets du bas : ligne + boutons # ------------------------------------------------------------------- # boutons boutAide=QPushButton(_(u" Aide")) boutAide.setIcon(QIcon("Icones/icone_aide_128.png")) self.connect(boutAide, SIGNAL("clicked()"), self.afficherAide) self.boutSauverInfos=QPushButton(_(u" Sauver infos (.txt)")) # Show infos (.txt) ==> Sauver infos (.txt) self.boutSauverInfos.setIcon(QIcon("Icones/icone_appliquer_128.png")) self.boutSauverInfos.setEnabled(False) self.connect(self.boutSauverInfos, SIGNAL("clicked()"), self.sauverInfos) self.boutSauverExif=QPushButton(_(u" Sauver Exif (.html)")) # Save Exif (.html) ==> Sauver Exif (.html) self.boutSauverExif.setIcon(QIcon("Icones/icone_appliquer_128.png")) self.boutSauverExif.setEnabled(False) self.connect(self.boutSauverExif, SIGNAL("clicked()"), self.sauverExif) # ligne de séparation juste au dessus des boutons ligne = QFrame() ligne.setFrameShape(QFrame.HLine) ligne.setFrameShadow(QFrame.Sunken) vbox.addWidget(ligne) vbox.addSpacing(-5) # la ligne doit être plus près des boutons hbox=QHBoxLayout() hbox.addWidget(boutAide) hbox.addStretch() # espace entre les 2 boutons hbox.addWidget(self.boutSauverInfos) hbox.addWidget(self.boutSauverExif) vbox.addLayout(hbox) # affichage de la boîte principale self.setLayout(vbox) #---------------------------------------------------------------------------------------------------- # Signal de présence d'images dans ler widget de sélection -> appelle la fonction appliquer #---------------------------------------------------------------------------------------------------- self.connect(self.afficheurImgSource, SIGNAL("pictureChanged(int)"), self.modifImgSource) self.connect(self.afficheurImgSource, SIGNAL("pictureSelected()"), self.appliquer) def modifImgSource(self): """Les infos de la 1ère image de la liste sont affichées dans les onglets (soit les infos exif+texte, soit uniquement les infos texte""" # print "Images chargées" self.appliquer() def appliquer(self): """Un chemin de fichier-image a été enregistré -> affichage des infos et possibilité de sauvegarder""" #print '\nfonction imAccepte' EkdPrint(u'\nfonction imAccepte') #print "chemin image d'entrée:", self.chemin, type(self.chemin) # Récupération de la liste des fichiers chargés self.chemin=self.afficheurImgSource.getFile() # Récupération du chemin + image chargée et de l'extension fich, self.ext=os.path.splitext(self.chemin) #=== remplissage des onglets avec les infos disponibles ===# #||| infos texte |||# self.afficherInfos() # Activation du bouton pour sauvegarder les infos texte self.boutSauverInfos.setEnabled(True) #||| infos exif et infos texte ... avec conditions |||# imgExif=Image.open(self.chemin) # Seulement si l'extension sélectionnée est en JPEG (et que l'image chargée # contient des données EXIF), la fonction self.afficherExif est exécutée if self.ext in [".jpg", ".JPG", ".jpeg", ".JPEG"] and 'Profile-exif:' in self.InfosTxtImage: exifdata=imgExif._getexif() self.afficherExif() # La page infos exif est sélectionnée self.tab.setCurrentIndex(self.tabIndexExif) # Activation du bouton pour sauvegarder les infos exif self.boutSauverExif.setEnabled(True) # Seulement si l'extension sélectionnée est en JPEG (mais que l'image chargée ne # contient pas de données EXIF), la fonction self.afficherInfos est exécutée, # et rien n'est affiché dans la page Infos Exif elif self.ext in [".jpg", ".JPG", ".jpeg", ".JPEG"] and 'Profile-exif:' not in self.InfosTxtImage: # La page infos exif se vide complètement (lignes et colonnes) self.table.setRowCount(0) self.table.setColumnCount(0) self.afficherInfos() # La page infos texte est sélectionnée self.tab.setCurrentIndex(self.tabIndexText) # Non activation du bouton pour sauvegarder les infos exif self.boutSauverExif.setEnabled(False) # Seulement si l'extension sélectionnée est tout autre que JPEG (c'est à # dire/et que l'image chargée ne contient pas de données EXIF), la fonction # self.afficherInfos est exécutée, et rien n'est affiché dans la page Infos Exif elif self.ext not in [".jpg", ".JPG", ".jpeg", ".JPEG"]: self.table.setRowCount(0) self.table.setColumnCount(0) self.afficherInfos() self.tab.setCurrentIndex(self.tabIndexText) # Activation du bouton pour sauvegarder les infos exif self.boutSauverExif.setEnabled(False) # Dégriser les onglets self.tab.setEnabled(True) # L'image n'a jamais été affichée self.drapeauImage = 0 def afficherInfos(self): """afficher les infos venant d'ImageMagick""" # Formats d'images acceptés (aussi ici en tout début) --> Rectifié le 25/12/08 if self.ext in [".jpg", ".JPG", ".jpeg", ".JPEG", ".png", ".PNG", ".gif", ".GIF", ".tif", ".TIF", ".tiff", ".TIFF", ".ppm", ".PPM", ".bmp", ".BMP"]: self.listeInfo=[] chemin = self.chemin.encode("UTF8") #self.ImageInfos=os.popen('identify -verbose'+' '+"\""+chemin+"\"").readlines() commande = 'identify -verbose \"%s\"' % chemin processus = QProcess() processus.start(commande) if (not processus.waitForStarted()): raise (u"Erreur de récupération des informations") if ( not processus.waitForFinished()): raise (u"Erreur de récupération des informations") # QString enlevé car pose de gros problèmes #self.ImageInfos = QString(processus.readAllStandardOutput()) self.ImageInfos = processus.readAllStandardOutput() # N'affichera que les infos humainement compréhensibles # (dans le cas d'une image avec données EXIF) self.listeInfo=[] for parcInfo in self.ImageInfos: # Ignore les données commençant par ... if parcInfo.startswith("0x0") or parcInfo.startswith(" Components Configuration:") or parcInfo.startswith(" Components Configuration:") or parcInfo.startswith(" Maker Note:") or parcInfo.startswith(" CFA Pattern:") or parcInfo.startswith(" User Comment:") or parcInfo.startswith(" File Source:") or parcInfo.startswith(" unknown:") or parcInfo.startswith(" Scene Type:") or parcInfo.startswith(" Print Image Matching:"): pass else: self.listeInfo.append(parcInfo) a=_(u"Vous ne pouvez sélectionner/afficher les infos que d'une image à la fois.\n==========================================================\nLes formats d'images acceptés sont: .jpg, .jpeg, .png, .gif, .tif, .tiff, .ppm, .bmp. Pour une image avec des informations EXIF, seulement le format .jpg (ou .jpeg, .JPG, .JPEG) est accepté.\n==========================================================\n") # Affichage dans zoneTexte self.InfosTxtImage=''.join(self.listeInfo) self.zoneTexte.setText(a+"\n"+self.InfosTxtImage) else: messageErreur=QMessageBox(self) messageErreur.setText(_(u"Une erreur est apparue! Cette extension (%s) n'est pas supportée" % self.ext)) messageErreur.setWindowTitle(_(u"Erreur")) messageErreur.setIcon(QMessageBox.Critical) messageErreur.exec_() def sauverInfos(self): """Sauver les infos (sous forme de fichier texte)""" try: # Utilisation de la nouvelle boîte de dialogue de sauvegarde suffix="" # Boîte de dialogue pour sauvegarder (nom du nouveau fichier) sauver = EkdSaveDialog(self, mode="image", suffix=suffix, title=_(u"Sauver"), multiple=False) sauver = sauver.getFile() if not sauver: return enrTxt=open(sauver+'.txt', 'wb') enrTxt.write(self.InfosTxtImage.encode("UTF8")) enrTxt.flush() enrTxt.close() except: messageErreur=QMessageBox(self) messageErreur.setText(_(u"Une erreur est apparue!")) messageErreur.setWindowTitle(_(u"Erreur")) messageErreur.setIcon(QMessageBox.Critical) messageErreur.exec_() def miseAJourTable(self, nbrCol, donnees): """ On met à jour la table exif """ self.table.clear() self.table.setRowCount(len(donnees)) self.table.setColumnCount(nbrCol) self.table.setHorizontalHeaderLabels([_(u"Paramètre"), _(u"Valeur")]) self.table.setAlternatingRowColors(True) self.table.setEditTriggers(QTableWidget.NoEditTriggers) self.table.setSelectionMode(QTableWidget.ExtendedSelection) for indexL, ligne in enumerate(donnees): for indexC, element in enumerate(ligne): item = QTableWidgetItem(str(element)) item.setTextAlignment(Qt.AlignCenter) self.table.setItem(indexL, indexC, item) #print indexL, indexC, element EkdPrint(u"%s %s %s" % (indexL, indexC, element)) self.table.resizeColumnsToContents() def afficherExif(self): """Afficher les infos Exif""" try : # ----------------------------------------------------------------------- # Extraction des donnees EXIF # ----------------------------------------------------------------------- # Aide de Terry Carroll sur la liste de diffusion : # image-sig at python.org . # Avec une petite adaptation personnelle. Merci à Terry Carroll. imgExif=Image.open(self.chemin.encode("UTF8")) # Recupération des infos EXIF exifdata=imgExif._getexif() listeExif=[] for keyExif, valueExif in zip(exifdata.keys(), exifdata.values()): try: if ExifTags.TAGS[keyExif] in ['UserComment', 'ComponentsConfiguration', 'CFAPattern', 'FileSource', 'SceneType', 'MakerNote', '\x01\x02\x03CompressedBitPerPixel']: pass else : ImageExif=(str(ExifTags.TAGS[keyExif]), valueExif) listeExif.append(ImageExif) except KeyError: pass # Changement de l'affichage pour l'enregistrement, et ce pour une # meilleure lecture des données EXIF. affExif=[] for exifAff in listeExif: if exifAff[0].startswith("ApertureValue"): exifAffnouveau='f/'+str(round(float(exifAff[1][0])/float(exifAff[1][1]), 1)) affExif.append(('ApertureValue', exifAffnouveau)) elif exifAff[0].startswith("CompressedBitsPerPixel"): exifAffnouveau=str(int(exifAff[1][0])/int(exifAff[1][1]))+' bit/pixel' affExif.append(('CompressedBitsPerPixel', exifAffnouveau)) elif exifAff[0].startswith("ExposureTime"): exifAffnouveau=str(round(float(exifAff[1][0])/float(exifAff[1][1]), 3))+' ('+str(exifAff[1][0]/exifAff[1][0])+'/'+str(exifAff[1][1]/exifAff[1][0])+' sec)' affExif.append(('ExposureTime',exifAffnouveau)) elif exifAff[0].startswith("FNumber"): exifAffnouveau='f/'+str(round(float(exifAff[1][0])/float(exifAff[1][1]), 1))+' (diaphragm value)' affExif.append(('FNumber', exifAffnouveau)) elif exifAff[0].startswith("FocalLength"): exifAffnouveau=str(round(float(exifAff[1][0])/float(exifAff[1][1]), 2))+' mm' affExif.append(('FocalLength', exifAffnouveau)) elif exifAff[0].startswith("FocalPlaneXResolution"): exifAffnouveau=str(round(float(exifAff[1][0])/float(exifAff[1][1]), 1)) affExif.append(('FocalPlaneXResolution', exifAffnouveau)) elif exifAff[0].startswith("FocalPlaneYResolution"): exifAffnouveau=str(round(float(exifAff[1][0])/float(exifAff[1][1]), 1)) affExif.append(('FocalPlaneYResolution', exifAffnouveau)) elif exifAff[0].startswith("MaxApertureValue"): exifAffnouveau='f/'+str(round(float(exifAff[1][0])/float(exifAff[1][1]), 1)) affExif.append(('MaxApertureValue', exifAffnouveau)) elif exifAff[0].startswith("XResolution"): exifAffnouveau=str(int(exifAff[1][0])/int(exifAff[1][1]))+' dpi' affExif.append(('XResolution', exifAffnouveau)) elif exifAff[0].startswith("YResolution"): exifAffnouveau=str(int(exifAff[1][0])/int(exifAff[1][1]))+' dpi' affExif.append(('YResolution', exifAffnouveau)) else: affExif.append(exifAff) # Ttes les infos Exif (tags Exif) obtenu(es) avec # intégration du code html pour affichage ds tableau. # Si on utilisait unicode pour les traductions, val[0] et val[1] # seraient à remplacer par _(unicode(val[0])) et _(unicode(val[1])) ExifHtml=['<tr>\n<td bgcolor="9dc3d9">'+str(val[0])+'</td>\n<td>'+str(val[1])+'</td>\n</tr>\n' for val in affExif] ExifHtml.sort() # ---------------------------------------------------- # Conditions de dimensions de l'image pour l'affichage # ---------------------------------------------------- # Recuperation de la valeur du poids de l'image poidsDebutImgExif=int(os.stat(self.chemin).st_size) # Calcul du poids en kilo-octet de l'image self.poidsFinImgExif=int(poidsDebutImgExif)/1000 # Recuperation du chemin de chargement de la photo chemEnrExif=os.path.dirname(self.chemin) # Recuperation du nom de la photo self.nomImageExif=os.path.basename(self.chemin).encode("UTF8") # On recupere la taille de l'image self.widthImgExif, self.heightImgExif=imgExif.size # Si la dimension la + grde est inférieure ou égale à 640 if max(self.widthImgExif, self.heightImgExif)<=640 : self.widthImgExif, self.heightImgExif # Si la dimension la + grde est supérieure à 640 if max(self.widthImgExif, self.heightImgExif)>640 : # Calcul du ratio de l'image ratioWHexif=float(max(self.widthImgExif, self.heightImgExif))/float(min(self.widthImgExif, self.heightImgExif)) # La dimension la + grde MaxWHexif=max(self.widthImgExif, self.heightImgExif) # On assigne automatiquement la dimension la + # grde à 640 pour affichage ds le fichier html MaxWHexif=float(640) # Calcul de la dimension la + petite MinWHexif=MaxWHexif/ratioWHexif # Si La dimension la + grde est supérieure à 640 et # la largeur est supérieure ou égale à la hauteur if MaxWHexif==float(640) and self.widthImgExif>=self.heightImgExif : self.widthImgExif=640 self.heightImgExif=int(round(MinWHexif)) # Si la hauteur est supérieure à la largeur if self.heightImgExif>self.widthImgExif : self.heightImgExif=640 self.widthImgExif=int(round(MinWHexif)) #---------------------------------------------------------- # mise en forme des données dans un tableau QTableWidget() #---------------------------------------------------------- self.miseAJourTable(2, listeExif) # ----------------------------------------------------- # Affichage de l'exif ds le code et ds un tableau . # ----------------------------------------------------- # --> Quelques traductions : # -------------------------- # NAME OF THE IMAGE --> NOM DE L'IMAGE # WEIGHT OF THE IMAGE --> POIDS DE L'IMAGE # kb --> ko (kilobytes --> kilo-octets) # EXIF INFORMATIONS ABOUT THE IMAGE --> INFORMATIONS EXIF SUR L'IMAGE # ExifVersion --> Version Exif # DateTime --> Date et heure # DateTimeOriginal --> Date et heure (d'origine) # DateTimeDigitized --> Date et heure (numerisation) # ExifImageWidth --> Dimension X en pixels # ExifImageHeight --> Dimension Y en pixels # XResolution --> Resolution X # YResolution --> Resolution Y # Make --> Constructeur # Model --> Modele # READ ATTENTIVELY WHAT IS JUST DOWN --> LISEZ ATTENTIVEMENT CE QUI # SE TROUVE EN DESSOUS # Your image has to be exactly in the same directory as your html file, # otherwise the image in question will not appear in this page . --> # Votre image doit se trouver exactement dans le même répertoire que # votre fichier html, autrement l'image en question n'apparaîtra pas # dans cette page . # This page html was generated by # <a href='http://ekd.tolosano.info'>EKD</a> (post-production # software for videos and images) . --> Cette page html a été générée # par <a href='http://ekd.tolosano.info'>EKD</a> (logiciel de # post-production pour les vidéos et les images) . #---------------------------------------------- # Construction du tableau html avant affichage #---------------------------------------------- self.infosExifPourHtml=u'<table border=1 bgcolor="f6d79a" width=100% align=center bordercolor="black">\n<tr>\n<tr>\n<td colspan="2" bgcolor="black" align="center"><font color="white">'+_(u"NOM DE L'IMAGE")+u'</td>\n</tr>\n<tr>\n<td colspan="2">'+self.nomImageExif+u'</td>\n</tr>\n<tr>\n<td colspan="2" bgcolor="black" align="center"><font color="white">'+_(u"POIDS DE L'IMAGE")+u'</td>\n</tr>\n<tr>\n<td colspan="2">'+str(self.poidsFinImgExif)+_(u' kb')+u'</td>\n</tr>\n<tr>\n<td colspan="2" bgcolor="black" align="center"><font color="white">'+_(u"INFORMATIONS EXIF SUR L'IMAGE")+u'</td>\n</tr>\n'+''.join(ExifHtml)+u'</tr>\n<tr>\n<td colspan="2" bgcolor="black" align="center"><font color="white">'+_(u"LISEZ ATTENTIVEMENT CE QUI SE TROUVE EN DESSOUS")+u'</td>\n</tr>\n<tr>\n<td colspan="2">'+_(u"Votre image doit se trouver exactement dans le même répertoire que le fichier html, autrement l'image en question n'apparaîtra pas dans la page.")+u'</td>\n</tr>\n<tr>\n<td colspan="2" bgcolor="black" align="center"><font color="white">'+_(u"Cette page html a été génerée par <a href='http://ekd.tuxfamily.org'>EKD</a> (logiciel de post-production pour vidéos et images).")+u'</td>\n</tr>\n</table>' except : messageErreur=QMessageBox(self) messageErreur.setText(_(u"Il s'est produit un problème !. Certainement que les photos de cet appareil ne contiennent pas des données importantes (comme par exemple 'Date et heure').")) messageErreur.setWindowTitle(_(u"Erreur")) messageErreur.setIcon(QMessageBox.Critical) messageErreur.exec_() def sauverExif(self): """Sauver les infos Exif (sous forme de fichier html)""" try: # Utilisation de la nouvelle boîte de dialogue de sauvegarde suffix="" # Boîte de dialogue pour sauvegarder (nom du nouveau fichier) sauver = EkdSaveDialog(self, mode="image", suffix=suffix, title=_(u"Sauver"), multiple=False) sauver = sauver.getFile() if not sauver: return enrHtml=open(sauver+'.html', 'wb') enrHtml.write('<!DOCTYPE html PUBLIC "-//IETF//DTD HTML 2.0//EN">\n<html>\n<head>\n<meta http-equiv=content-type content="text/html; charset=UTF-8">\n<title>EXIF infos</title>\n</head>\n<body bgcolor="d0cfcd" text="#000000" link="lightblue" vlink="orange">\n<center><IMG src="'+self.nomImageExif+'" '+ 'width="'+str(self.widthImgExif)+'" height="'+str(self.heightImgExif)+'" border="0"></center>\n<h4>'+self.infosExifPourHtml+'</h4>\n</body>\n</html>') enrHtml.flush() enrHtml.close() # Si on enregistre le fichier html dans le répertoire où la photo a été # chargée, il faut vérifier si l'image chargée est déjà présente dans le rep. # de sauvegarde if os.path.exists(os.path.dirname(sauver)+os.sep+os.path.basename(self.chemin)) is False: # Copie de la 1ère image chargée dans le répertoire de sauvegarde # ... cela est utile pour que le fichier html généré s'affiche bien shutil.copy(self.chemin, os.path.dirname(sauver)) else: pass except: messageErreur=QMessageBox(self) messageErreur.setText(_(u"Une erreur est apparue!")) messageErreur.setWindowTitle(_(u"Erreur")) messageErreur.setIcon(QMessageBox.Critical) messageErreur.exec_() def afficherAide(self): """Boîte de dialogue de l'aide du cadre Image > Informations (txt ou Exif)""" # Gestion de l'aide via EkdAide messageAide=EkdAide(parent=self) messageAide.setText(_(u"<p><b>Vous pouvez ici afficher les informations (en format texte) d'images et/ou les informations EXIF (Exchangeable Image File Format) de prises de vues d'appareils photographiques numériques. Vous pouvez de même exporter ces informations sous forme de fichier html (en vue d'être publié sur internet, par exemple).</b></p><p><b>Voici une défintion de EXIF selon Wikipédia: 'L’Exchangeable image file format ou Exif est une spécification de format de fichier pour les images utilisées par les appareils photographiques numériques.</b></p><p><b>Les balises de métadonnées définies dans le format Exif standard couvrent un large éventail de données, dont:<br><ol><li>Information de la date et de l’heure. Les appareils numériques enregistrent la date et l’heure de la prise de vue et l’insèrent dans les métadonnées.</li><li>Les réglages de l’appareil. Cela comprend des informations statiques telles que la marque et le modèle de l’appareil et des informations variables telles que l’orientation, l’ouverture, la vitesse d’obturation, la longueur de focale, la sensibilité ... .</li><li>Description et information des droits d’auteur.'</b></li></ol></p><p><b><font color='green'>Attention: comme vous pouvez le constater, les boutons 'Sauver infos (.txt)' et 'Sauver Exif (.html)' sont dans un premier temps grisés (ils deviendront actifs dès lors que vous aurez chargé une image).</font></b></p><p>Dans l'onglet <b>'Image source'</b> cliquez sur le bouton <b>Ajouter</b>, une boîte de dialogue apparaît, sur la partie gauche sélectionnez le répertoire (au besoin dépliez les sous-répertoires), allez chercher votre/vos image(s). Si vous voulez sélectionner plusieurs images d'un coup, maintenez la touche <b>CTRL</b> (ou <b>SHIFT</b>) du clavier enfoncée (tout en sélectionnant vos images), cliquez sur <b>Ajouter</b>.</p><p>Vous avez maintenant la possibilité de consulter les informations sur l'image dans les onglets <b>'Infos Texte'</b> et/ou <b>'Infos Exif'</b>. <b><font color='red'>Il faut savoir que les informations délivrées sont celles de l'image que vous aurez sélectionné dans l'onglet Image(s) source</font></b></p><p>Vous pouvez sauvegarder les informations texte et/ou EXIF (suivant l'image/photo chargée), pour ce faire cliquez sur le bouton <b>'Sauver infos (.txt)'</b> ou <b>'Sauver Exif (.html)'</b>, dans la boîte de dialogue sélectionnez le répertoire de sauvegarde, indiquez votre <b>'Nom de fichier'</b>, cliquez sur le bouton <b>'Enregistrer'</b>. <b><font color='red'>De même ici les informations qui seront enregistrées seront celles de l'image sélectionnée dans l'onglet Image(s) source.</font></b></p>")) messageAide.show() def save(self) : self.afficheurImgSource.saveFileLocation(self.idSection) def load(self) : self.afficheurImgSource.loadFileLocation(self.idSection) self.appliquer()
class Image_Divers_RenomImg(QWidget): # ----------------------------------- # Cadre accueillant les widgets de : # Image >> Divers >> Renommer images # ----------------------------------- def __init__(self, geometry): QWidget.__init__(self) # Boite d'alignement vertical vbox=QVBoxLayout(self) #=== Création des répertoires temporaires ===# # Gestion du repertoire tmp avec EkdConfig self.repTampon = EkdConfig.getTempDir() + os.sep + "tampon" + os.sep + "image_divers_renommage" + os.sep if os.path.isdir(self.repTampon) is False: os.makedirs(self.repTampon) # Au cas où le répertoire existait déjà et qu'il n'était pas vide # -> purge (simple précausion) for toutRepCompo in glob.glob(self.repTampon+'*.*'): os.remove(toutRepCompo) #=== Drapeaux ===# # Une conversion (même partielle) a-t-elle eu lieu après le chargement des images? (1: vrai) # Est-ce que des images sources ont été modifiées? (c'est-à-dire ajoutées ou supprimées) self.modifImageSource = 0 #=== Variable de configuration ===# # Fonctions communes à plusieurs cadres du module Image self.base = Base() # Gestion de la configuration via EkdConfig # Paramètres de configuration self.config = EkdConfig # Identifiant du cadre self.idSection = "image_renommer" # Log du terminal self.base.printSection(self.idSection) # Fonction appelant la fenêtre principale self.mainWindowFrameGeometry = geometry self.listeImgSource = [] # là où s'afficheront les infos self.zoneTexte = QTextEdit("") if PYQT_VERSION_STR < "4.1.0": self.zoneTexte.setText = self.zoneTexte.setPlainText self.zoneTexte.setReadOnly(True) self.tabwidget=QTabWidget() # Boîte de combo self.comboClassement=QComboBox() self.listeComboClassement=[(_(u'Par ordre de sélection'), 'ord_select'),\ (_(u'Par ordre alpha-numérique'), 'ord_apha_num'),\ (_(u'Par ordre de prise de vue (données Exif): ordre croissant'), 'ord_exif_oc'),\ (_(u'Par ordre de prise de vue (données Exif): ordre décroissant'), 'ord_exif_dc')] # Insertion de l'ordre de classement des images/photos dans la combo box for i in self.listeComboClassement: self.comboClassement.addItem(i[0],QVariant(i[1])) self.connect(self.comboClassement, SIGNAL("currentIndexChanged(int)"), self.classement) #=== 1er onglet ===# self.framNbreImg=QFrame() vboxReglage=QVBoxLayout(self.framNbreImg) self.grid = QGridLayout() self.grid.addWidget(QLabel(_(u"Traitement à partir de l'image (numéro)")), 0, 0) self.spin1=SpinSlider(1, 10000, 1, '', self) self.grid.addWidget(self.spin1, 0, 1) self.connect(self.spin1, SIGNAL("valueChanged(int)"), self.changeValNbreImg_1) self.grid.addWidget(QLabel(_(u"Nombre de chiffres après le nom de l'image")), 1, 0) self.spin2=SpinSlider(3, 18, 6, '', self) self.grid.addWidget(self.spin2, 1, 1) self.connect(self.spin2, SIGNAL("valueChanged(int)"), self.changeValNbreImg_1) # Demandé par Marc de la liste lprod # "J'aurais été interressé par un paramètre supplémentaire pour définir l'incrément (ou le pas ...). # En fait cela permettrait d'insérer (donc classer) facilement les photos issues d'appareils # différents mais traitant les mêmes sujets. En fait, je voudrais réaliser une série finale des # photos prises avec mon APN, mon camescope et l'APN de mon épouse. Je numérote de 10 en 10 la # plus grosse série de photos et viens insérer les autres au bon endroit sans avoir à utiliser # des indices a, b, c, etc." self.grid.addWidget(QLabel(_(u"Valeur d'incrément (passage d'une image à l'autre)")), 2, 0) #self.spin3=SpinSlider(1, 1000, 1, 'increment', self) # Ne fonctionne pas self.spin3=SpinSlider(1, 1000, 1, '', self) self.grid.addWidget(self.spin3, 2, 1) self.connect(self.spin3, SIGNAL("valueChanged(int)"), self.changeValNbreImg_1) self.grid.addWidget(QLabel(_(u"Classement")), 3, 0) self.grid.addWidget(self.comboClassement, 3, 1) self.grid.setAlignment(Qt.AlignHCenter) vboxReglage.addLayout(self.grid) vboxReglage.addStretch() #=== 2ème onglet ===# # infos - logs self.zoneAffichInfosImg = QTextEdit("") if PYQT_VERSION_STR < "4.1.0": self.zoneAffichInfosImg.setText = self.zoneAffichInfosImg.setPlainText self.zoneAffichInfosImg.setReadOnly(True) self.framImg=QFrame() vboxReglage=QVBoxLayout(self.framImg) vboxReglage.addWidget(self.zoneAffichInfosImg) self.framImg.setEnabled(False) # Là où s'afficheront les images self.afficheurImgSource=SelectWidget(geometrie = self.mainWindowFrameGeometry) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "image" # Défini le type de fichier source. self.typeSortie = "image" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurImgSource # Fait le lien avec le sélecteur de fichier source. self.indexTabImgSource = self.tabwidget.addTab(self.afficheurImgSource, _(u'Image(s) source')) self.indexTabReglage=self.tabwidget.addTab(self.framNbreImg, _(u'Réglages')) self.indexTabInfo=self.tabwidget.addTab(self.framImg, _(u'Infos')) vbox.addWidget(self.tabwidget) # ------------------------------------------------------------------- # widgets du bas : ligne + boutons Aide et Appliquer # ------------------------------------------------------------------- # Boutons boutAidetRenomImg=QPushButton(_(u" Aide")) boutAidetRenomImg.setIcon(QIcon("Icones/icone_aide_128.png")) self.connect(boutAidetRenomImg, SIGNAL("clicked()"), self.afficherAide) self.boutApp=QPushButton(_(u" Renommer")) self.boutApp.setIcon(QIcon("Icones/icone_appliquer_128.png")) self.connect(self.boutApp, SIGNAL("clicked()"), self.appliquer) # Ligne de séparation juste au dessus des boutons ligne = QFrame() ligne.setFrameShape(QFrame.HLine) ligne.setFrameShadow(QFrame.Sunken) vbox.addWidget(ligne) vbox.addSpacing(-5) # la ligne doit être plus près des boutons hbox=QHBoxLayout() hbox.addWidget(boutAidetRenomImg) hbox.addStretch() # espace entre les 2 boutons hbox.addWidget(self.boutApp) self.boutApp.setEnabled(False) vbox.addLayout(hbox) # Affichage de la boîte principale self.setLayout(vbox) #---------------------------------------------------------------------------------------------------- # Signal de présence d'images dans ler widget de sélection -> modifie le statut des boutons d'action #---------------------------------------------------------------------------------------------------- self.connect(self.afficheurImgSource, SIGNAL("pictureChanged(int)"), self.modifBoutonsAction) def modifBoutonsAction(self, i): "On active ou désactive les boutons d'action selon s'il y a des images ou pas dans le widget de sélection" self.boutApp.setEnabled(i) self.modifImageSource = 1 def changeValNbreImg_1(self): ## #print "Traitement a partir de l'image (numero):", self.spin1.value() EkdPrint(u"Traitement a partir de l'image (numero): %s" % self.spin1.value()) #print "Nombre de chiffres apres le nom de l'image:", self.spin2.value() EkdPrint(u"Nombre de chiffres apres le nom de l'image: %s" % self.spin2.value()) # Demandé par Marc de la liste lprod #print "Valeur d'incrément (passage d'une image à l'autre):", self.spin3.value() EkdPrint(u"Valeur d'incrément (passage d'une image à l'autre): %s" % self.spin3.value()) def classement(self, i): """Récup/affichage ds le terminal de l'index de self.comboClassement""" idCombo=str(self.comboClassement.itemData(i).toString()) if idCombo=='ord_select': #print 'Par ordre de sélection' EkdPrint(u'Par ordre de sélection') elif idCombo=='ord_apha_num': #print 'Par ordre alpha-numérique' EkdPrint(u'Par ordre alpha-numérique') elif idCombo=='ord_exif_oc': #print 'Par ordre de prise de vue (données Exif): ordre croissant' EkdPrint(u'Par ordre de prise de vue (données Exif): ordre croissant') elif idCombo=='ord_exif_dc': #print 'Par ordre de prise de vue (données Exif): ordre décroissant' EkdPrint(u'Par ordre de prise de vue (données Exif): ordre décroissant') # Gestion du nombre d'images à traiter # Si l'utilisateur charge une seule image l'image sera renommée comme ceci: le_titre.extension # Si l'utilisateur charge plusieurs images les images seront renommées comme ceci: le_titre_001.extension, # le_titre_002.extension, le_titre_003.extension, ... def appliquer(self): """Appliquer le renommage des images""" self.a1='###########################\n' self.b1=_('Fichiers avant renommage')+':\n' self.c1='###########################\n\n' # Récupération de la liste des fichiers chargés self.listeImgSource=self.afficheurImgSource.getFiles() # Nombre d'éléments présents dans la liste nbreElem=len(self.listeImgSource) # Copie par une boucle des images chargées par l'utilisateur dans le rep. # tampon --> shutil.copy(...) (on ne va pas utiliser # os.rename(source, destination) mais shutil.move(source, destination)) # car avec os.rename les fichiers source sont détruits, ce qui est une très # mauvaise chose. De plus si on utilise os.rename on obtient l'erreur: OSError: # [Errno 18] Lien croisé invalide # for n in range(nbreElem): shutil.copy(self.listeImgSource[n], self.repTampon) # On récupère le nom des fichiers chargés (seulement le nom, pas le chemin) # --> là il faut absolument garder les noms des fichiers exactement dans # l'ordre dans lequel ils ont été chargés. On ne peut pas utiliser glob, ni # os.listdir(...) car ils rangent dans un ordre qui n'est pas le bon # (certainement car ils utilisent à la base un dictionnaire). l_pre_imgSource = [os.path.basename(fich) for fich in self.listeImgSource] # On concatène le chemin (juste le chemin) du rep. tampon avec les fichiers l_imgSource = [self.repTampon+add for add in l_pre_imgSource] # Récup du classement choisi par l'utilisateur (par la QComboBox) i = self.comboClassement.currentIndex() classemt = self.comboClassement.itemData(i).toString() if classemt == 'ord_select': pass elif classemt == 'ord_apha_num': l_imgSource.sort() else: l_imgSource.sort() listeExif = [] for fichier in l_imgSource: # Sélection du chemin (sans extension) et de l'extension elle-même fich, ext = os.path.splitext(fichier) # Condition de sélection de l'extension pour EXIF et ouverture image try: if ext in [".jpg", ".JPG", ".jpeg", ".JPEG"]: imgExif = Image.open(fichier) else: # Si l'utilisateur charge des images avec des extensions autres que ... erreur_0 = QMessageBox.critical(self,_(u"Par ordre de prise de vue (données Exif)"),_(u"<p><b>Vous ne pouvez charger que des images avec des extensions jpg, JPG, jpeg ou JPEG (!).</b></p>"), QMessageBox.Yes) if erreur_0 == QMessageBox.Yes: return # Données brutes contenues dans le dictionnaire exifdata = imgExif._getexif() # Certaines images délivrent None à _getexif(), dans ce/ces cas # la boîte de dialogue erreur_1 est affichée et le traitement # est stoppé. Les 2 boîtes de dialogue (erreur_1 et erreur_2) # sont utilisées dans 2 cas différents (même si le message # d'erreur délivré est le même) if exifdata == None: erreur_1 = QMessageBox.critical(self,_(u"Par ordre de prise de vue (données Exif)"),_(u"<p>Une ou plusieurs de vos image(s) possède(nt) de fausses donnée(s) Exif.</p><p><b>Le traitement demandé ne pourra pas se dérouler avec succès.</b></p><p><b>Veuillez, s'il vous plaît (la prochaine fois), charger des photos avec des données Exif correctes.</b></p>"), QMessageBox.Yes) if erreur_1 == QMessageBox.Yes: return # Si on charge des images ne possédant pas de données Exif # ou de fausses images avec données Exif (comme par exemple # les copies d'écran faites par Gimp, qui elles, bizarre ! # possèdent des données Exif inexploitables) tout se ferme except: erreur_2 = QMessageBox.critical(self,_(u"Par ordre de prise de vue (données Exif)"),_(u"<p>Une ou plusieurs de vos image(s) possède(nt) de fausses donnée(s) Exif.</p><p><b>Le traitement demandé ne pourra pas se dérouler avec succès.</b></p><p><b>Veuillez, s'il vous plaît (la prochaine fois), charger des photos avec des données Exif correctes.</b></p>"), QMessageBox.Yes) if erreur_2 == QMessageBox.Yes: return # Récup des clés et valeurs du dictionnaire for keyExif, valueExif in zip(exifdata.keys(), exifdata.values()): try: # On ne récupère que le tag DateTime if ExifTags.TAGS[keyExif] == 'DateTimeOriginal': listeExif.append(valueExif.split(' ')) except KeyError: pass # Split pour récup des données de la sorte: [..., [année, mois, jour], [heure, min, sec], ...] listeSplit = [parc_2.split(':') for parc_1 in listeExif for parc_2 in parc_1] # Récup de la 1ère sous-liste uniquement index pair # [[année_1, mois_1, jour_1], [année_2, mois_2, jour_2], ...] listeIndexPair = [n for n in listeSplit if listeSplit.index(n) % 2 == 0] # Récup de la 2ème sous-liste uniquement index impair # [[heure_1, min_1, sec_1], [heure_2, min_2, sec_2], ...] listeIndexImpair = [n for n in listeSplit if listeSplit.index(n) % 2 != 0] # Fusion des 2 listes listeReunion = zip(listeIndexPair, listeIndexImpair) listeDate = [] for nb, group in enumerate(zip(listeReunion, l_imgSource)): # Les données de temps affichées ne sont pas directement exploitables # Il y a des choses à transformer ... surtout si on trouve des '00' # ------------------- annee = group[0][0][0] if annee[0] == '0': annee = annee[-1:] else: annee = annee # ------------------- mois = group[0][0][1] if mois[0] == '0': mois = mois[-1:] else: mois = mois # ------------------- jour = group[0][0][2] if jour[0] == '0': jour = jour[-1:] else: jour = jour # ------------------- heure = group[0][1][0] if heure[0] == '0': heure = heure[-1:] else: heure = heure # ------------------- minute = group[0][1][1] if minute[0] == '0': minute = minute[-1:] else: minute = minute # ------------------- seconde = group[0][1][2] if seconde[0] == '0': seconde = seconde[-1:] else: seconde = seconde # Liste remplie de la sorte: [..., [annee, mois, jour, # heure, minute, seconde, index, chemin_image], ...] listeDate.append([int(annee), int(mois), int(jour), int(heure), int(minute), int(seconde), nb, group[1]]) listeDate.sort() # Sélection de chaque élément à l'indice 7 dans les sous-listes, # c'est à dire le chemin de l'image (ordre croissant) listeOrdreDateCrois = [ordre for pre_ordre in listeDate for ordre in pre_ordre if pre_ordre.index(ordre) / 7 == 1] l_imgSource = listeOrdreDateCrois # Si l'utilisateur choisit l'ordre décroissant, le même # traitement est effectué, mais l'ordre de la liste finale # est inversé if classemt == 'ord_exif_dc': l_imgSource.reverse() # Demandé par Marc de la liste lprod # La liste liste_increment_2 contient les données comme ceci: # [(indice img 1, incrément img 1), (indice img 2, incrément img 2), # (indice img 3, incrément img 3), (indice img 4, incrément img 4), ...] liste_increment_1 = [c_pas_1*self.spin3.value() for c_pas_1 in range(nbreElem)] liste_increment_2 = [(inc, c_pas_2) for inc, c_pas_2 in enumerate(liste_increment_1)] # Récupération du chemin + vidéo chargée et de l'extension # (la première image de la liste) fich, ext=os.path.splitext(self.listeImgSource[0]) # Utilisation de la nouvelle boîte de dialogue de sauvegarde # Boîte de dialogue pour sauvegarder (nom du nouveau fichier) suffix="" sauver = EkdSaveDialog(self, mode="image", suffix=suffix, title=_(u"Sauver"), multiple=True) sauver = sauver.getFile() # Pour la version windows # Autrement les fichiers seront (a la fin) renommes comme ceci: # a.jpg_000001.jpg, a.jpg_000002.jpg, ... alors que la ils seront renommes # comme ceci (c'est quand meme mieux !): a_000001.jpg, a_000002.jpg, ... # Uniquement pour windows if os.name == 'nt': sauver, ext_sauv = os.path.splitext(sauver) if not sauver: return listeSauve=[] if len(self.listeImgSource)==1: # Renommage et sauvegarde shutil.move(l_imgSource[0], sauver+ext) listeSauve.append(sauver+ext) elif len(self.listeImgSource)>1: # Barre de progression dans une fenêtre séparée . Attention la fenêtre # de la barre se ferme dès que la progression est terminée . En cas de # process très court, la fenêtre peut n'apparaître que très brièvement # (voire pas du tout si le temps est très très court) . self.progress=QProgressDialog(_(u"Opération en cours ..."), _(u"Annuler conversion"), 0, 100) self.progress.setWindowTitle(_(u'EnKoDeur-Mixeur . Fenêtre de progression')) # Attribution des nouvelles dimensions self.progress.setMinimumWidth(500) self.progress.setMinimumHeight(100) # setGeometry est utilisé ici uniquement pour le placement à la position 0, 0 # je ne suis pas un adepte de la position 0,0 :-P (Romain) #self.progress.setGeometry(QRect(0, 0, 500, 100)) self.progress.show() # Demandé par Marc de la liste lprod # Le parcours se fait maintenant par la liste liste_increment_2 (le contenu # de cette liste est précisé est précisé plus haut). Ceci pour mettre en # pratique l'icrémentation des images, c'est à dire de pouvoir les renommer # comme ceci: a_0001.jpg, a_0004.jpg, a_0007.jpg, a_0010.jpg, ... --> quand # l'utilisateur change la Valeur d'incrément (passage d'une image à l'autre) # à 3 (ce n'est qu'un exemple) for parc, increment in liste_increment_2: # Renommage et sauvegarde # Uniquement pour Linux et MacOSX if os.name in ['posix', 'mac']: shutil.move(l_imgSource[parc], sauver+'_'+string.zfill(increment+self.spin1.value(), self.spin2.value())+ext) # Uniquement pour windows elif os.name == 'nt': # --------------- # On avait prealablement (uniquement sous windows) l'erreur suivante: # WindowsError: [Error 32] Le processus ne peut pas acceder au fichier car ce # fichier est utilise par un autre processus: ... # --------------- # La solution pour la version windows a ete trouvee ici: # http://www.nabble.com/Windows-process-ownership-trouble-td18110819.html # --> try: ... except WindowsError: ... # --------------- try: shutil.move(l_imgSource[parc], sauver+'_'+string.zfill(increment+self.spin1.value(), self.spin2.value())+ext) except WindowsError: pass # Gestion du nombre d'images à traiter listeSauve.append(sauver+'_'+string.zfill(increment+self.spin1.value(), self.spin2.value())+ext) # -------------------------------------------- # Affichage de la progression (avec # QProgressDialog) ds une fenêtre séparée . val_pourc=((parc+1)*100)/nbreElem # Bouton Cancel pour arrêter la progression donc le process if (self.progress.wasCanceled()): break self.progress.setValue(val_pourc) QApplication.processEvents() # -------------------------------------------- self.a2='###########################\n' self.b2=_(u'Fichiers après renommage')+':\n' self.c2='###########################\n\n' # Affichage dans zoneTexte des fichiers avant et après renommage self.zoneAffichInfosImg.setText(self.a1+self.b1+self.c1+"\n".join(self.listeImgSource)+'\n\n'+self.a2+self.b2+self.c2+"\n".join(listeSauve)) self.framImg.setEnabled(True) self.tabwidget.setCurrentIndex(self.indexTabInfo) def afficherAide(self): """ Boîte de dialogue de l'aide du cadre Image > Renommer """ # Nouvelle fenêtre d'aide messageAide=EkdAide(parent=self) messageAide.setText(tr(u"<p><b>Vous pouvez ici renommer un lot d'images. Pour information (et ce dans l'onglet Réglages), vous pouvez choisir un ordre spécifique dans le classement de vos images (les deux derniers choix dans la liste font référence aux données EXIF des photographies numériques).</b></p><p>Dans l'onglet <b>'Images sources'</b> cliquez sur le bouton <b>Ajouter</b>, une boîte de dialogue apparaît, sur la partie gauche sélectionnez le répertoire (au besoin dépliez les sous-répertoires), allez chercher vos image(s). Si vous voulez sélectionner plusieurs images d'un coup, maintenez la touche <b>CTRL</b> (ou <b>SHIFT</b>) du clavier enfoncée (tout en sélectionnant vos images), cliquez sur <b>Ajouter</b>.<p></p>Dans l'onglet <b>'Réglages'</b> faites les réglages du <b>'Traitement à partir de l'image (numéro)'</b> et du <b>'Nombre de chiffres après le nom de l'image' <font color='red'>(la plupart du temps les valeurs par défaut suffisent)</font></b></p>.<p>Une fois tout ceci fait, cliquez sur le bouton <b>'Renommer'</b>, sélectionnez le répertoire de sauvegarde, indiquez votre <b>'Nom de fichier'</b>, cliquez sur le bouton <b>'Enregistrer'</b>.</p><p>Après le traitement vous pouvez immédiatement voir l'affichage du résultat avant et après renommage des fichiers par l'onglet <b>'Infos'</b>.</p>")) messageAide.show() def save(self) : self.afficheurImgSource.saveFileLocation(self.idSection) EkdConfig.set(self.idSection, u'spin1', unicode(self.spin1.value())) EkdConfig.set(self.idSection, u'spin2', unicode(self.spin2.value())) EkdConfig.set(self.idSection, u'spin3', unicode(self.spin3.value())) EkdConfig.set(self.idSection, u'comboClassement', unicode(self.comboClassement.currentIndex())) def load(self) : self.afficheurImgSource.loadFileLocation(self.idSection) self.spin1.setValue(int(EkdConfig.get(self.idSection, 'spin1'))) self.spin2.setValue(int(EkdConfig.get(self.idSection, 'spin2'))) self.spin3.setValue(int(EkdConfig.get(self.idSection, 'spin3'))) self.comboClassement.setCurrentIndex(int(EkdConfig.get(self.idSection, 'comboClassement')))
def __init__(self): vbox=QVBoxLayout() #=== Variable de configuration ===# self.config=EkdConfig #=== Identifiant de la classe ===# self.idSection = "animation_separer_audio_et_video" super(Animation_SeparVideoEtAudio, self).__init__('vbox', titre=_(u"Séparation audio/vidéo")) # module de animation self.printSection() ## On utilise le selecteur d'image pour les vidéos from gui_modules_image.selectWidget import SelectWidget # Là où on naviguera entre les fichiers self.afficheurVideoSource=SelectWidget(extensions = ["*.avi", "*.mpg", "*.mpeg", "*.mjpeg", "*.flv", "*.mp4", "*.dv", "*.vob"], mode="texte", video = True) # Onglets self.indexVideoSource = self.add(self.afficheurVideoSource, _(u'Video(s) source')) self.connect(self.afficheurVideoSource,SIGNAL("fileSelected"),self.getFile) self.connect(self.afficheurVideoSource, SIGNAL("pictureChanged(int)"), self.getFile) ## ------------------------------------------------------------------- ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "video" # Défini le type de fichier source. self.typeSortie = ["video","audio"] # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurVideoSource # Fait le lien avec le sélecteur de fichier source. # ------------------------------------------------------------------- # Boîte de groupe "Réglage de sortie de l'encodage" # ------------------------------------------------------------------- groupBox = QGroupBox("") self.layoutReglage = QHBoxLayout(groupBox) #=== Widget qui seront inclus dans la boite de réglage ===# # boite de combo self.combo=QComboBox() # le self sert à afficher des informations sur les items à partir de la fonction # listeCombo=[(texte, clé QVariant)...] self.listeCombo=[ (_(u'Vidéo et audio') ,'video&audio'),\ (_(u'Vidéo seulement') ,'video'),\ (_(u'Audio seulement') , 'audio'),] # Insertion des codecs de compression dans la combo box for i in self.listeCombo: self.combo.addItem(i[0],QVariant(i[1])) self.connect(self.combo, SIGNAL("activated(int)"), self.sauverTypeExtraction) self.layoutReglage.addWidget(self.combo, 0, Qt.AlignHCenter) # On tient-compte du paramètre de configuration try: typ = self.config.get(self.idSection,'type_extraction') indice = 0 # indice de la ligne de self.listeCombo correspondant au type d'ordre for i in listeCombo: if i[1]!=typ: indice += 1 else: break self.combo.setCurrentIndex(indice) except: self.combo.setCurrentIndex(0) self.add(groupBox, _(u"Réglages")) self.addPreview(nomPreview = u"Visualiser / écouter - Vidéo / Audio", light = False, mode = "Video+Audio") self.addLog()
class Animation_SeparVideoEtAudio(Base): # ---------------------------------------------------------------------------------------- # Cadre accueillant les widgets de : Animation >> Séparer le flux vidéo et le flux audio # ---------------------------------------------------------------------------------------- def __init__(self): vbox=QVBoxLayout() #=== Variable de configuration ===# self.config=EkdConfig #=== Identifiant de la classe ===# self.idSection = "animation_separer_audio_et_video" super(Animation_SeparVideoEtAudio, self).__init__('vbox', titre=_(u"Séparation audio/vidéo")) # module de animation self.printSection() ## On utilise le selecteur d'image pour les vidéos from gui_modules_image.selectWidget import SelectWidget # Là où on naviguera entre les fichiers self.afficheurVideoSource=SelectWidget(extensions = ["*.avi", "*.mpg", "*.mpeg", "*.mjpeg", "*.flv", "*.mp4", "*.dv", "*.vob"], mode="texte", video = True) # Onglets self.indexVideoSource = self.add(self.afficheurVideoSource, _(u'Video(s) source')) self.connect(self.afficheurVideoSource,SIGNAL("fileSelected"),self.getFile) self.connect(self.afficheurVideoSource, SIGNAL("pictureChanged(int)"), self.getFile) ## ------------------------------------------------------------------- ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "video" # Défini le type de fichier source. self.typeSortie = ["video","audio"] # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurVideoSource # Fait le lien avec le sélecteur de fichier source. # ------------------------------------------------------------------- # Boîte de groupe "Réglage de sortie de l'encodage" # ------------------------------------------------------------------- groupBox = QGroupBox("") self.layoutReglage = QHBoxLayout(groupBox) #=== Widget qui seront inclus dans la boite de réglage ===# # boite de combo self.combo=QComboBox() # le self sert à afficher des informations sur les items à partir de la fonction # listeCombo=[(texte, clé QVariant)...] self.listeCombo=[ (_(u'Vidéo et audio') ,'video&audio'),\ (_(u'Vidéo seulement') ,'video'),\ (_(u'Audio seulement') , 'audio'),] # Insertion des codecs de compression dans la combo box for i in self.listeCombo: self.combo.addItem(i[0],QVariant(i[1])) self.connect(self.combo, SIGNAL("activated(int)"), self.sauverTypeExtraction) self.layoutReglage.addWidget(self.combo, 0, Qt.AlignHCenter) # On tient-compte du paramètre de configuration try: typ = self.config.get(self.idSection,'type_extraction') indice = 0 # indice de la ligne de self.listeCombo correspondant au type d'ordre for i in listeCombo: if i[1]!=typ: indice += 1 else: break self.combo.setCurrentIndex(indice) except: self.combo.setCurrentIndex(0) self.add(groupBox, _(u"Réglages")) self.addPreview(nomPreview = u"Visualiser / écouter - Vidéo / Audio", light = False, mode = "Video+Audio") self.addLog() def getFile(self): ''' # On utilise la nouvelle interface de récupération des vidéos Récupération de la vidéo source selectionnée ''' self.chemin = self.afficheurVideoSource.getFile() self.boutApp.setEnabled(True) self.mplayer.setEnabled(True) self.mplayer.setVideos([self.chemin]) self.radioSource.setChecked(True) self.radioSource.setEnabled(True) self.boutApp.setEnabled(True) if self.idSection == "animation_filtresvideo": self.boutApercu.setEnabled(True) self.filtreDecouper.setButtonEnabled(True) self.emit(SIGNAL("loaded")) return self.chemin def sauverTypeExtraction(self,i): """ Sauvegarde du mode d'extraction """ idCombo= str(self.combo.itemData(i).toStringList()[0]) self.config.set(self.idSection,'type_extraction',idCombo) def afficherAide(self): """ Boîte de dialogue de l'aide """ super(Animation_SeparVideoEtAudio,self).afficherAide(_(u"<p><b>Ici vous pouvez séparer le canal vidéo et le canal audio dans une vidéo (bien entendu une vidéo qui contient du son).</b></p><p>Dans l'onglet <b>'Vidéo(s) source'</b> cliquez sur le bouton <b>Ajouter</b>, une boîte de dialogue apparaît, sur la partie gauche sélectionnez le répertoire (au besoin dépliez les sous-répertoires), allez chercher la/les vidéo(s). Si vous voulez sélectionner plusieurs vidéos d'un coup, maintenez la touche <b>CTRL</b> (ou <b>SHIFT</b>) du clavier enfoncée (tout en sélectionnant vos vidéos), cliquez sur <b>Ajouter</b>.</p><p>Vous pouvez dès lors sélectionner une vidéo dans la liste et la visionner (par le bouton juste à la droite de cette liste), vous noterez que vous pouvez visionner la vidéo en quatre tiers, en seize neuvième ou avec les proportions d'origine de la vidéo (w;h). De même si vous le désirez, vous pouvez obtenir des informations complètes sur la vidéo sélectionnée, et ce par le bouton <b>'Infos'</b> (en bas).</p><p>Dans l'onglet <b>'Réglages'</b> sélectionnez le mode d'extraction (<b>'Vidéo et audio'</b>, <b>'Vidéo seulement'</b> ou <b>'Audio seulement'</b>).</p><p>Une fois tout ceci fait, cliquez sur le bouton <b>'Appliquer'</b>, sélectionnez le répertoire de sauvegarde, indiquez votre <b>'Nom de fichier'</b>, cliquez sur le bouton <b>'Enregistrer'</b> et attendez le temps de la conversion. A la fin cliquez sur le bouton <b>'Voir les informations d'encodage'</b> et fermez cette dernière fenêtre après avoir vu les informations en question.</p><p>Dans l'onglet <b>'Visualiser / écouter - Vidéo / Audio'</b>, dans le cas d'une extaction <b>'Vidéo et audio'</b> vous pouvez à la fois visionner la vidéo d'un côté et écouter l'audio de l'autre, dans le cas d'une extraction de la <b>'Vidéo seulement'</b>, vous pouvez lire la vidéo résultante (le bouton de lecture audio reste grisé), et dans le dernier cas résultat de l'extraction de l'<b>'Audio seulement'</b>, vous pourrez écouter la piste audio extraite (dans ce cas la zone de lecture vidéo est cette fois-ci grisée). Dans les deux cas (extraction vidéo et audio, et vidéo seulement) le visionnement de la vidéo d'origine se fait en sélectionnant <b>'vidéo(s) source(s)'</b>, et <b>'vidéo convertie'</b> pour la vidéo résultante de l'extraction. Vous pouvez lire les deux en même temps, en cliquant sur le bouton <b>'Comparateur de vidéos'</b></p><p>L'onglet <b>'Infos'</b> vous permet de voir les vidéos chargées (avec leurs chemins exacts) avant et après conversion.</p>")) def ouvrirSource(self, nomEntree=None): """Récupération du chemin de la vidéo sélectionnée et activation de certains widgets""" # Récupération du chemin de la vidéo chemin = self.recupSource(nomEntree) if not chemin: return # Affichage du chemin + nom de fichier dans la ligne d'édition self.ligneEditionSource.setText(chemin) self.mplayer.listeVideos = [chemin] self.mplayer.setEnabled(True) self.boutApp.setEnabled(True) def extraireVideo(self, cheminVideoEntre, SortieVideoSFA, laisserOuvert=1): """extraction de la vidéo du fichier""" try: mencoder = WidgetMEncoder("extractionvideo", cheminVideoEntre, SortieVideoSFA, laisserOuvert=laisserOuvert) mencoder.setWindowTitle(_(u"Extraction vidéo")) mencoder.exec_() except: messageErrAnEnc=QMessageBox(self) messageErrAnEnc.setText(_(u"Problème d'extraction vidéo (mencoder)")) messageErrAnEnc.setWindowTitle(_(u"Error")) messageErrAnEnc.setIcon(QMessageBox.Warning) messageErrAnEnc.exec_() return def extraireAudio(self, cheminVideoEntre, SortieAudioSFA, laisserOuvert=1): """extraction de l'audio du fichier""" ffmpeg = WidgetFFmpeg("extractionaudio", cheminVideoEntre, SortieAudioSFA, laisserOuvert=laisserOuvert) ffmpeg.setWindowTitle(_(u"Extraction audio")) ffmpeg.exec_() def appliquer(self, nomSortie=None, ouvert=1): """ appelle la boite de dialogue de sélection de fichier à sauver et appel de la fonction de séparation audio-vidéo """ # quel est l'index du dernier item sélectionné de la boîte de combo? index=self.combo.currentIndex() ## On utilise la nouvelle interface de récupération des vidéos # Récupération du chemin source chemin = self.getFile() if not nomSortie: # suffix du fichier actif if self.combo.currentIndex() == 2 : suffix = ".wav" else : suffix=os.path.splitext(chemin)[1] saveDialog = EkdSaveDialog(self, mode="video", suffix=suffix, title=_(u"Sauver")) cheminFichierEnregistrerVideo = saveDialog.getFile() else: # module séquentiel cheminFichierEnregistrerVideo = nomSortie if not cheminFichierEnregistrerVideo: return ########################################################################################################################### # chemins complets de sortie vidéo et audio SortieVideoSFA = cheminFichierEnregistrerVideo # Vérification de l'extension du nom de fichier indiqué. if cheminFichierEnregistrerVideo.endswith(".wav") : SortieAudioSFA = cheminFichierEnregistrerVideo else : SortieAudioSFA = cheminFichierEnregistrerVideo + ".wav" ########################################################################################################################## #=== Separation du fichier video ===# idCombo = self.listeCombo[index][1] #print "Combo :", idCombo EkdPrint(u"Combo : %s" % idCombo) # extraction self.radioSource.setEnabled(True) self.radioSource.setChecked(False) self.radioConvert.setEnabled(True) self.boutCompare.setEnabled(True) # extraction audio et vidéo if idCombo == 'video&audio': # le 3ème argument sert à fermer automatiquement la fenêtre d'encodage #print "Extraction Vidéo" EkdPrint(u"Extraction Vidéo") self.extraireVideo(chemin, SortieVideoSFA, 0) #print "Extraction Audio" EkdPrint(u"Extraction Audio") self.extraireAudio(chemin, SortieAudioSFA, ouvert) self.lstFichiersSortie = [SortieVideoSFA] self.mplayerA.setEnabled(True) self.mplayerA.setVideos([SortieAudioSFA]) self.infoLog(None, chemin, None, [SortieVideoSFA,SortieAudioSFA]) self.radioConvert.setChecked(True) # extraction vidéo elif idCombo == 'video': self.extraireVideo(chemin, SortieVideoSFA, ouvert) self.lstFichiersSortie = [SortieVideoSFA] self.mplayerA.setEnabled(False) self.infoLog(None, chemin, None, SortieVideoSFA) self.radioConvert.setChecked(True) # extraction audio elif idCombo == 'audio': self.extraireAudio(chemin, SortieAudioSFA, ouvert) self.lstFichiersSortie = None self.mplayerA.setEnabled(True) self.mplayerA.setVideos([SortieAudioSFA]) self.infoLog(None, chemin, None, [SortieVideoSFA,SortieAudioSFA]) self.infoLog(None, chemin, None, SortieAudioSFA) self.radioSource.setChecked(True) self.radioConvert.setEnabled(False) self.boutCompare.setEnabled(False) return SortieVideoSFA def sequentiel(self, entree, sortie, ouvert=0): """Utile dans le module du même nom. Applique les opérations de la classe. Retourne le vrai nom du fichier de sortie""" self.ouvrirSource(entree) return self.appliquer(sortie, ouvert) def sequentielReglage(self): """Utile dans le module du même nom. Récupère le widget de réglage associé à l'identifiant donné en 1er argument. Retourne l'instance du widget de réglage""" return self.groupReglage def saveFiles(self): ''' # On sauvegarde la liste des fichiers chargés ''' self.afficheurVideoSource.saveFileLocation(self.idSection) def loadFiles(self): ''' # On sauvegarde la liste des fichiers chargés ''' self.afficheurVideoSource.loadFileLocation(self.idSection) def load(self): ''' Chargement de la configuration de tous les objets ''' self.loadFiles() def save(self): ''' Sauvegarde de la configuration de tous les objets ''' self.saveFiles()
def __init__(self, statusBar, geometry): QWidget.__init__(self) # ------------------------------- # Parametres généraux du widget # ------------------------------- #=== tout sera mis dans une boîte verticale ===# vbox=QVBoxLayout(self) #=== Création des répertoires temporaires ===# # Utilisation de EkdConfig self.repTampon = EkdConfig.getTempDir() + os.sep if os.path.isdir(self.repTampon) is False: os.makedirs(self.repTampon) # Au cas où le répertoire existait déjà et qu'il n'était pas vide # -> purge (simple précausion) for toutRepCompo in glob.glob(self.repTampon+'*.*'): os.remove(toutRepCompo) #=== Drapeaux ===# # Une conversion (même partielle) a-t-elle eu lieu après le chargement des images? (1: vrai) # Est-ce que des images ont été converties et qu'elles n'ont pas encore été montrées? # Marche aussi quand la conversion a été arrêté avant la fin de la 1ère image self.conversionImg = 0 # Est-ce qu'une prévisualisation a été appelée? self.previsualImg = 0 # Est-ce que des images sources ont été modifiées? (c'est-à-dire ajoutées ou supprimées) self.modifImageSource = 0 # Délai avant conversion self.timer = QTimer() self.connect(self.timer, SIGNAL('timeout()'), self.sonderTempsActuel) # Fonctions communes à plusieurs cadres du module Image self.base = Base() # Gestion de la configuration via EkdConfig # Paramètres de configuration self.config = EkdConfig # Identifiant du cadre self.idSection = "image_changer_format" # Log du terminal self.base.printSection(self.idSection) # Fonction appelant la fenêtre principale self.mainWindowFrameGeometry = geometry self.listeImgSource = [] self.listeImgDestin = [] #------------------------ # Onglets et stacked #------------------------ self.tabwidget=QTabWidget() #=== 1er onglet ===# self.framReglage=QFrame() vboxReglage=QVBoxLayout(self.framReglage) # boite de combo self.comboReglage=QComboBox() self.listeComboReglage=[(_(u'JPEG (.jpg)'), '.jpg'),\ (_(u'JPEG (.jpeg)'), '.jpeg'),\ (_(u'PNG (.png)'), '.png'),\ (_(u'GIF (.gif)'), '.gif'),\ (_(u'BMP (.bmp)'), '.bmp'),\ (_(u'PPM (.ppm)'), '.ppm'),\ (_(u'TIFF (.tiff)'), '.tiff'),\ (_(u'TIF (.tif)'), '.tif')] # Se trouve directement dans l'onglet Réglages self.grid = QGridLayout() self.grid.addWidget(QLabel(_(u"Traitement à partir de l'image (numéro)")), 0, 0) self.spin1=SpinSlider(1, 100000, 1, '', self) self.grid.addWidget(self.spin1, 0, 1) self.connect(self.spin1, SIGNAL("valueChanged(int)"), self.changeValNbreImg_1) self.grid.addWidget(QLabel(_(u"Nombre de chiffres après le nom de l'image")), 1, 0) self.spin2=SpinSlider(3, 18, 6, '', self) self.grid.addWidget(self.spin2, 1, 1) self.connect(self.spin2, SIGNAL("valueChanged(int)"), self.changeValNbreImg_1) self.grid.setAlignment(Qt.AlignHCenter) vboxReglage.addLayout(self.grid) vboxReglage.addStretch() # Insertion des formats dans la combo box for i in self.listeComboReglage: self.comboReglage.addItem(i[0],QVariant(i[1])) self.connect(self.comboReglage, SIGNAL("currentIndexChanged(int)"), self.changerComboReglage) # Affiche l'entrée de la boite de combo inscrite dans un fichier de configuration self.base.valeurComboIni(self.comboReglage, self.config, self.idSection, 'format') self.grid2 = QGridLayout() # Label qualité pour la qualité (compression) lors de la sauvegarde en JPEG self.labQualite=QLabel(_(u"Qualité")) self.labQualite.hide() self.grid2.addWidget(QLabel(_(u'Type de format après traitement')), 0, 0) self.grid2.addWidget(self.comboReglage, 0, 1) self.grid2.addWidget(self.labQualite, 2, 0) # Réglage de la qualité pour la qualité (compression) lors de la sauvegarde en JPEG self.spin3=SpinSlider(1, 100, 75, '', self) self.spin3.hide() i = self.comboReglage.currentIndex() idCombo=str(self.comboReglage.itemData(i).toStringList()[0]) if idCombo in ['.jpg', '.jpeg']: self.labQualite.show() self.spin3.show() else: self.labQualite.hide() self.spin3.hide() self.grid2.addWidget(self.spin3, 2, 1) self.connect(self.spin3, SIGNAL("valueChanged(int)"), self.changeQualitePourJPEG) self.grid2.setAlignment(Qt.AlignHCenter) vboxReglage.addLayout(self.grid2) vboxReglage.addStretch() #=== 2ème onglet ===# # infos - logs self.zoneAffichInfosImg = QTextEdit("") if PYQT_VERSION_STR < "4.1.0": self.zoneAffichInfosImg.setText = self.zoneAffichInfosImg.setPlainText self.zoneAffichInfosImg.setReadOnly(True) self.framImg=QFrame() vboxReglage=QVBoxLayout(self.framImg) vboxReglage.addWidget(self.zoneAffichInfosImg) self.framImg.setEnabled(False) # ------------------------------------------------- # Onglets d'affichage image source et destination # ------------------------------------------------- # Là où s'afficheront les images self.afficheurImgSource=SelectWidget(geometrie = geometry) self.afficheurImgDestination=Lecture_VisionImage(statusBar) self.indexTabImgSource = self.tabwidget.addTab(self.afficheurImgSource, _(u'Image(s) source')) self.indexTabReglage=self.tabwidget.addTab(self.framReglage, _(u'Réglages')) self.indexTabImgDestin=self.tabwidget.addTab(self.afficheurImgDestination, _(u'Image(s) après traitement')) self.indexTabInfo=self.tabwidget.addTab(self.framImg, _(u'Infos')) vbox.addWidget(self.tabwidget) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "image" # Défini le type de fichier source. self.typeSortie = "image" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurImgSource # Fait le lien avec le sélecteur de fichier source. #------------------ # Widgets du bas #------------------ # boutons boutAide=QPushButton(_(u" Aide")) boutAide.setIcon(QIcon("Icones/icone_aide_128.png")) boutAide.setFocusPolicy(Qt.NoFocus) self.connect(boutAide, SIGNAL("clicked()"), self.afficherAide) self.boutApPremImg = QPushButton(_(u" Voir le résultat")) self.boutApPremImg.setIcon(QIcon("Icones/icone_visionner_128.png")) self.boutApPremImg.setFocusPolicy(Qt.NoFocus) self.boutApPremImg.setEnabled(False) self.connect(self.boutApPremImg, SIGNAL("clicked()"), self.visu_1ere_img) self.boutApp=QPushButton(_(u" Appliquer")) self.boutApp.setIcon(QIcon("Icones/icone_appliquer_128.png")) self.boutApp.setFocusPolicy(Qt.NoFocus) self.boutApp.setEnabled(False) self.connect(self.boutApp, SIGNAL("clicked()"), self.appliquer0) # Ligne de séparation juste au dessus des boutons ligne = QFrame() ligne.setFrameShape(QFrame.HLine) ligne.setFrameShadow(QFrame.Sunken) vbox.addWidget(ligne) vbox.addSpacing(-5) # la ligne doit être plus près des boutons hbox=QHBoxLayout() hbox.addWidget(boutAide) hbox.addStretch() # espace entre les 2 boutons hbox.addWidget(self.boutApPremImg) hbox.addStretch() hbox.addWidget(self.boutApp) vbox.addLayout(hbox) self.setLayout(vbox) #------------------------------------------------ # Barre de progression dans une fenêtre séparée #------------------------------------------------ self.progress=QProgressDialog(_(u"Progression ..."), _(u"Arrêter le processus"), 0, 100) self.progress.setWindowTitle(_(u'EnKoDeur-Mixeur. Fenêtre de progression')) # Attribution des nouvelles dimensions self.progress.setMinimumWidth(500) self.progress.setMinimumHeight(100) self.connect(self.tabwidget, SIGNAL("currentChanged(int)"), self.fctTab) #---------------------------------------------------------------------------------------------------- # Signal de présence d'images dans ler widget de sélection -> modifie le statut des boutons d'action #---------------------------------------------------------------------------------------------------- self.connect(self.afficheurImgSource, SIGNAL("pictureChanged(int)"), self.modifBoutonsAction) #---------------------------------------------------------------------------------------------------- # Signal pour afficher ou ne pas afficher les widgets de changement de qualité pour les images #---------------------------------------------------------------------------------------------------- self.connect(self.comboReglage, SIGNAL("currentIndexChanged(int)"), self.changerQualJPEG)
class Image_Divers_ChangFormat(QWidget): """# ----------------------------------- # Cadre accueillant les widgets de : # Image >> Divers >> Changer format # Gestion de 16 formats de fichiers # -----------------------------------""" def __init__(self, statusBar, geometry): QWidget.__init__(self) # ------------------------------- # Parametres généraux du widget # ------------------------------- #=== tout sera mis dans une boîte verticale ===# vbox=QVBoxLayout(self) #=== Création des répertoires temporaires ===# # Utilisation de EkdConfig self.repTampon = EkdConfig.getTempDir() + os.sep if os.path.isdir(self.repTampon) is False: os.makedirs(self.repTampon) # Au cas où le répertoire existait déjà et qu'il n'était pas vide # -> purge (simple précausion) for toutRepCompo in glob.glob(self.repTampon+'*.*'): os.remove(toutRepCompo) #=== Drapeaux ===# # Une conversion (même partielle) a-t-elle eu lieu après le chargement des images? (1: vrai) # Est-ce que des images ont été converties et qu'elles n'ont pas encore été montrées? # Marche aussi quand la conversion a été arrêté avant la fin de la 1ère image self.conversionImg = 0 # Est-ce qu'une prévisualisation a été appelée? self.previsualImg = 0 # Est-ce que des images sources ont été modifiées? (c'est-à-dire ajoutées ou supprimées) self.modifImageSource = 0 # Délai avant conversion self.timer = QTimer() self.connect(self.timer, SIGNAL('timeout()'), self.sonderTempsActuel) # Fonctions communes à plusieurs cadres du module Image self.base = Base() # Gestion de la configuration via EkdConfig # Paramètres de configuration self.config = EkdConfig # Identifiant du cadre self.idSection = "image_changer_format" # Log du terminal self.base.printSection(self.idSection) # Fonction appelant la fenêtre principale self.mainWindowFrameGeometry = geometry self.listeImgSource = [] self.listeImgDestin = [] #------------------------ # Onglets et stacked #------------------------ self.tabwidget=QTabWidget() #=== 1er onglet ===# self.framReglage=QFrame() vboxReglage=QVBoxLayout(self.framReglage) # boite de combo self.comboReglage=QComboBox() self.listeComboReglage=[(_(u'JPEG (.jpg)'), '.jpg'),\ (_(u'JPEG (.jpeg)'), '.jpeg'),\ (_(u'PNG (.png)'), '.png'),\ (_(u'GIF (.gif)'), '.gif'),\ (_(u'BMP (.bmp)'), '.bmp'),\ (_(u'PPM (.ppm)'), '.ppm'),\ (_(u'TIFF (.tiff)'), '.tiff'),\ (_(u'TIF (.tif)'), '.tif')] # Se trouve directement dans l'onglet Réglages self.grid = QGridLayout() self.grid.addWidget(QLabel(_(u"Traitement à partir de l'image (numéro)")), 0, 0) self.spin1=SpinSlider(1, 100000, 1, '', self) self.grid.addWidget(self.spin1, 0, 1) self.connect(self.spin1, SIGNAL("valueChanged(int)"), self.changeValNbreImg_1) self.grid.addWidget(QLabel(_(u"Nombre de chiffres après le nom de l'image")), 1, 0) self.spin2=SpinSlider(3, 18, 6, '', self) self.grid.addWidget(self.spin2, 1, 1) self.connect(self.spin2, SIGNAL("valueChanged(int)"), self.changeValNbreImg_1) self.grid.setAlignment(Qt.AlignHCenter) vboxReglage.addLayout(self.grid) vboxReglage.addStretch() # Insertion des formats dans la combo box for i in self.listeComboReglage: self.comboReglage.addItem(i[0],QVariant(i[1])) self.connect(self.comboReglage, SIGNAL("currentIndexChanged(int)"), self.changerComboReglage) # Affiche l'entrée de la boite de combo inscrite dans un fichier de configuration self.base.valeurComboIni(self.comboReglage, self.config, self.idSection, 'format') self.grid2 = QGridLayout() # Label qualité pour la qualité (compression) lors de la sauvegarde en JPEG self.labQualite=QLabel(_(u"Qualité")) self.labQualite.hide() self.grid2.addWidget(QLabel(_(u'Type de format après traitement')), 0, 0) self.grid2.addWidget(self.comboReglage, 0, 1) self.grid2.addWidget(self.labQualite, 2, 0) # Réglage de la qualité pour la qualité (compression) lors de la sauvegarde en JPEG self.spin3=SpinSlider(1, 100, 75, '', self) self.spin3.hide() i = self.comboReglage.currentIndex() idCombo=str(self.comboReglage.itemData(i).toStringList()[0]) if idCombo in ['.jpg', '.jpeg']: self.labQualite.show() self.spin3.show() else: self.labQualite.hide() self.spin3.hide() self.grid2.addWidget(self.spin3, 2, 1) self.connect(self.spin3, SIGNAL("valueChanged(int)"), self.changeQualitePourJPEG) self.grid2.setAlignment(Qt.AlignHCenter) vboxReglage.addLayout(self.grid2) vboxReglage.addStretch() #=== 2ème onglet ===# # infos - logs self.zoneAffichInfosImg = QTextEdit("") if PYQT_VERSION_STR < "4.1.0": self.zoneAffichInfosImg.setText = self.zoneAffichInfosImg.setPlainText self.zoneAffichInfosImg.setReadOnly(True) self.framImg=QFrame() vboxReglage=QVBoxLayout(self.framImg) vboxReglage.addWidget(self.zoneAffichInfosImg) self.framImg.setEnabled(False) # ------------------------------------------------- # Onglets d'affichage image source et destination # ------------------------------------------------- # Là où s'afficheront les images self.afficheurImgSource=SelectWidget(geometrie = geometry) self.afficheurImgDestination=Lecture_VisionImage(statusBar) self.indexTabImgSource = self.tabwidget.addTab(self.afficheurImgSource, _(u'Image(s) source')) self.indexTabReglage=self.tabwidget.addTab(self.framReglage, _(u'Réglages')) self.indexTabImgDestin=self.tabwidget.addTab(self.afficheurImgDestination, _(u'Image(s) après traitement')) self.indexTabInfo=self.tabwidget.addTab(self.framImg, _(u'Infos')) vbox.addWidget(self.tabwidget) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "image" # Défini le type de fichier source. self.typeSortie = "image" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurImgSource # Fait le lien avec le sélecteur de fichier source. #------------------ # Widgets du bas #------------------ # boutons boutAide=QPushButton(_(u" Aide")) boutAide.setIcon(QIcon("Icones/icone_aide_128.png")) boutAide.setFocusPolicy(Qt.NoFocus) self.connect(boutAide, SIGNAL("clicked()"), self.afficherAide) self.boutApPremImg = QPushButton(_(u" Voir le résultat")) self.boutApPremImg.setIcon(QIcon("Icones/icone_visionner_128.png")) self.boutApPremImg.setFocusPolicy(Qt.NoFocus) self.boutApPremImg.setEnabled(False) self.connect(self.boutApPremImg, SIGNAL("clicked()"), self.visu_1ere_img) self.boutApp=QPushButton(_(u" Appliquer")) self.boutApp.setIcon(QIcon("Icones/icone_appliquer_128.png")) self.boutApp.setFocusPolicy(Qt.NoFocus) self.boutApp.setEnabled(False) self.connect(self.boutApp, SIGNAL("clicked()"), self.appliquer0) # Ligne de séparation juste au dessus des boutons ligne = QFrame() ligne.setFrameShape(QFrame.HLine) ligne.setFrameShadow(QFrame.Sunken) vbox.addWidget(ligne) vbox.addSpacing(-5) # la ligne doit être plus près des boutons hbox=QHBoxLayout() hbox.addWidget(boutAide) hbox.addStretch() # espace entre les 2 boutons hbox.addWidget(self.boutApPremImg) hbox.addStretch() hbox.addWidget(self.boutApp) vbox.addLayout(hbox) self.setLayout(vbox) #------------------------------------------------ # Barre de progression dans une fenêtre séparée #------------------------------------------------ self.progress=QProgressDialog(_(u"Progression ..."), _(u"Arrêter le processus"), 0, 100) self.progress.setWindowTitle(_(u'EnKoDeur-Mixeur. Fenêtre de progression')) # Attribution des nouvelles dimensions self.progress.setMinimumWidth(500) self.progress.setMinimumHeight(100) self.connect(self.tabwidget, SIGNAL("currentChanged(int)"), self.fctTab) #---------------------------------------------------------------------------------------------------- # Signal de présence d'images dans ler widget de sélection -> modifie le statut des boutons d'action #---------------------------------------------------------------------------------------------------- self.connect(self.afficheurImgSource, SIGNAL("pictureChanged(int)"), self.modifBoutonsAction) #---------------------------------------------------------------------------------------------------- # Signal pour afficher ou ne pas afficher les widgets de changement de qualité pour les images #---------------------------------------------------------------------------------------------------- self.connect(self.comboReglage, SIGNAL("currentIndexChanged(int)"), self.changerQualJPEG) def modifBoutonsAction(self, i): "On active ou désactive les boutons d'action selon s'il y a des images ou pas dans le widget de sélection" self.boutApp.setEnabled(i) self.boutApPremImg.setEnabled(i) self.modifImageSource = 1 def changerComboReglage(self, i): """Récup/affichage ds le terminal de l'index de self.comboReglage""" #print self.comboReglage.currentText() EkdPrint(u"%s" % self.comboReglage.currentText()) self.config.set(self.idSection, 'format', self.listeComboReglage[i][1]) def changerQualJPEG(self): ''' Changement de la qualité pour les images jpeg à l'enregistrement ''' # Si on sélectionne le format JPEG (avec extension .jpg ou .jpeg) dans la liste # déroulante, on peut régler la qualité du JPEG pour la sauvegarde if self.comboReglage.currentIndex() in [0, 1]: self.labQualite.show() self.spin3.show() # Si on sélectionne tous les autres formats, les widgets n'apparaissent pas else: self.labQualite.hide() self.spin3.hide() def changeValNbreImg_1(self): """Gestion du nombre d'images à traiter""" #print "Traitement a partir de l'image (numero):", self.spin1.value() EkdPrint(u"Traitement a partir de l'image (numero): %s" % self.spin1.value()) #print "Nombre de chiffres apres le nom de l'image:", self.spin2.value() EkdPrint(u"Nombre de chiffres apres le nom de l'image: %s" % self.spin2.value()) def changeQualitePourJPEG(self): #print "Compression JPEG, qualité:", self.spin3.value() EkdPrint(u"Compression JPEG, qualité: %s" % self.spin3.value()) def fctTab(self, i): "Affichage d'une ou plusieurs images converties" # Cela ne concerne que l'onglet de visualisation des images après leur conversion if i == self.indexTabImgDestin: if self.conversionImg: # Affichage si on sauvegarde par le bouton Appliquer et sauver #print "La conversion vient d'avoir lieu -> affichage des images du lot de destination" EkdPrint(u"La conversion vient d'avoir lieu -> affichage des images du lot de destination") cheminImages = os.path.dirname(self.listeImgDestin[0]) liste = [] for fichier in self.listeImgDestin: liste.append(os.path.basename(fichier)) self.afficheurImgDestination.updateImages(liste, cheminImages) elif not self.boutApp.isEnabled() or self.modifImageSource: # Si le bouton de conversion n'est pas actif, c'est qu'il n'y a plus d'image source # -> on n'a plus de raison de maintenir des images dans l'afficheur de résultat # Si les images sources ont été modifiées, on purge aussi l'afficheur de résultat self.afficheurImgDestination.updateImages([]) self.conversionImg = 0 self.modifImageSource = 0 def metaFctTab(self, i): """Changement d'onglet (conçu pour sélectionner les onglets "Images Source" après le chargement de nouvelles images sources ou "Images Après Traitement" après la conversion). But: s'assurer que la fonction associée au QTabWidget (affichage d'images, grisage/dégrisage du curseur...) sera bien appliquée même si on est déjà sur le bon onglet""" if self.tabwidget.currentIndex()!=i: self.tabwidget.setCurrentIndex(i) else: self.fctTab(i) def visu_1ere_img(self): """Fonction pour faire une simulation de rendu (avec les réglages opérés dans l'onglet Réglages) et ce à partir du bouton Aperçu à partir de la première image, toujours dans l'onglet Réglages. Pour les commentaires, se référer à la fonction chang_format juste en dessous""" # Récupération du fichier sélectionné par l'utilisateur (si pas de fichier # sélectionné par l'utilisateur, la 1ère image de la liste est prise) file = self.afficheurImgSource.getFile() if not file: return self.listeImgSource = [file] i = self.comboReglage.currentIndex() ext=self.comboReglage.itemData(i).toString() ext=str(ext).lower() # Formats (extensions) supportées: .bmp, .gif, .jpeg, .jpg, .mng, .pbm, .pgm, # .png, .ppm, .svg, .tif, .tiff, .xbm, .xpm formats = [".%s" % unicode(format).lower() \ for format in QImageReader.supportedImageFormats()] # Chemin+nom d'image pour la sauvegarde self.cheminCourantSauv = self.repTampon+'0_image_visu_'+string.zfill(1, 6)+ext # CONVERSION # Uniquement pour Linux et MacOSX if os.name in ['posix', 'mac']: # On sélectionne le 'Type de format après traitement' à JPEG (.jpg) # ou JPEG (.jpeg) le traitement se fait par Python Imaging Library if i in [0, 1]: im = Image.open(self.listeImgSource[0]).save(self.cheminCourantSauv, quality=self.spin3.value()) # Si on sélectionne les autres entrées, le traitement se fait par ImageMagick else: import locale # Conversion immédiate dans le rep tampon os.system(("convert "+"\""+self.listeImgSource[0]+"\""+' '+"\""+self.cheminCourantSauv+"\"").encode(locale.getdefaultlocale()[1])) # Uniquement pour windows elif os.name == 'nt': # Dans la version windows les autres entrees ne sont pas traitees # par ImageMagik mais directement par Python Imaging Library (car # par le traitement avec ImageMagick rien ne s'affiche, bizarre !!!) if i in [0, 1, 2, 3, 4, 5, 6, 7]: im = Image.open(self.listeImgSource[0]).save(self.cheminCourantSauv, quality=self.spin3.value()) # AFFICHAGE # Récup de l'extension chargée ext_chargee=os.path.splitext(self.listeImgSource[0])[1] # Si le format (l'extension) chargé et le format sélectionné pour la sortie # dans Réglages sont des formats supportés, l'image avec ce format est # simplement affichéé # ---------------------------------------------------------------------- # Aussi bizarre que cela puisse paraître (et .xpm est un format reconnu) # la conversion en xpm se fait bien mais l'image n'est pas lue dans le # lecteur --> alors conversion en jpeg ... et l'image est lue # ---------------------------------------------------------------------- if ext in formats: # Récupération de la liste contenant le chemin+fichier contenus # contenu dans le répertoire temporaire listeImgDestinVisuTmp_0=glob.glob(self.repTampon+'*.*') listeImgDestinVisuTmp_0.sort() # Elimination des fichiers parasites si multiples conversions # --> seulement l'extension de sortie sélectionnée est gardée for parctemp_0 in listeImgDestinVisuTmp_0: if os.path.splitext(parctemp_0)[1]!=ext: os.remove(parctemp_0) # Affichage de l'image temporaire # Ouverture d'une boite de dialogue affichant l'aperçu. # # Affichage par le bouton Voir le résultat visio = VisionneurEvolue(self.cheminCourantSauv) visio.redimenFenetre(self.mainWindowFrameGeometry, 1., 0.7) visio.exec_() return 0 def chang_format(self): """ Changer format (gestion de différents formats de fichiers) """ # Récupération de la liste des fichiers chargés self.listeChemin=self.afficheurImgSource.getFiles() # Récup du format sélectionné par l'utilisateur i = self.comboReglage.currentIndex() ext=self.comboReglage.itemData(i).toString() ext=str(ext).lower() #print "format:", ext EkdPrint(u"format: %s" % ext) nbreElem=len(self.listeChemin) # Liste pour affichage des images chargées (ds le tabwidget) listeAff_1=[] # Liste pour affichage des pages sauvegardées (ds le tabwidget) listeAff_2=[] # La page Image résultat devient visible #self.tabwidget.setCurrentIndex(self.indexImageResultat) # Liste des formats supportés pour l'affichage formats = [".%s" % unicode(format).lower() \ for format in QImageReader.supportedImageFormats()] #print "formats:", formats EkdPrint(u"formats: %s" % formats) process = QProcess(self) # Boucle principale for parc in range(nbreElem): # Chemin de sauvegarde vraiCheminSauv = self.chemDossierSauv+'_'+string.zfill(parc+self.spin1.value(), self.spin2.value())+ext # On sélectionne le 'Type de format après traitement' à JPEG (.jpg) ou JPEG (.jpeg) # Uniquement pour Linux et MacOSX if os.name in ['posix', 'mac']: # le traitement se fait par Python Imaging Library if i in [0, 1]: im = Image.open(self.listeChemin[parc]).save(vraiCheminSauv, quality=self.spin3.value()) # Uniquement pour windows elif os.name == 'nt': if i in [0, 1, 2, 3, 4, 5, 6, 7]: im = Image.open(self.listeChemin[parc]).save(vraiCheminSauv, quality=self.spin3.value()) # Bouton Cancel pour arrêter la progression donc le process if (self.progress.wasCanceled()): break # Si on sélectionne les autres entrées, le traitement se fait par ImageMagick else: # Enregistrement/conversion des formats sélectionnés process.start("convert "+"\""+self.listeChemin[parc]+"\" "+"\""+vraiCheminSauv+"\"") # Ajout des images par la variable vraiCheminSauv dans la liste self.listeImgDestin.append(vraiCheminSauv) listeAff_1.append(self.listeChemin[parc]) listeAff_2.append(vraiCheminSauv) # ================================================================== # # Calcule le pourcentage effectue a chaque passage et ce pour la # barre de progression . # --------------------------------------------- val_pourc=((parc+1)*100)/nbreElem # -------------------------------------------- # Affichage de la progression (avec # QProgressDialog) ds une fenêtre séparée self.progress.setValue(val_pourc) QApplication.processEvents() # Bouton Cancel pour arrêter la progression donc le process if (self.progress.wasCanceled()): break # -------------------------------------------- if not process.waitForStarted(3000): QMessageBox.warning(None, _(u"Erreur"), _(u"Bogue au lancement de la commande")) process.waitForFinished(-1) # Conditions d'affichage des images dans l'interface # Si le format est supporté pour l'affichage ... if ext in formats: # Affichage des images après traitement # # Changement d'onglet et fonctions associées self.conversionImg = 1 self.metaFctTab(self.indexTabImgDestin) # La liste pour l'affichage des images ds l'interface est # vidée pour que les images affichées ne s'amoncellent pas # si plusieurs rendus à la suite self.listeImgDestin=[] # Affichage des infos sur l'image ------------------------- # On implémente les chemins des fichiers dans une variable # pour préparer l'affichage des infos texte1=_(u" Image(s) chargée(s)") texte2=_(u" Image(s) convertie(s)") a='#'*36 self.infosImgProv_1=a+'\n#'+texte1+'\n'+a self.infosImgProv_2=a+'\n#'+texte2+'\n'+a # Images chargées for parcStatRendu_1 in listeAff_1: self.infosImgProv_1=self.infosImgProv_1+'\n'+parcStatRendu_1 # Pages sauvegardées for parcStatRendu_2 in listeAff_2: self.infosImgProv_2=self.infosImgProv_2+'\n'+parcStatRendu_2 # affichage des infos dans l'onglet self.zoneAffichInfosImg.setText(self.infosImgProv_1+'\n\n'+self.infosImgProv_2+'\n\n') self.framImg.setEnabled(True) # remise à 0 de la variable provisoire de log self.infosImgProv='' # --------------------------------------------------------- def sonderTempsActuel(self): """x ms après l'apparition de la boite de dialogue, on lance la conversion. But: faire en sorte que la boite de dialogue ait le temps de s'afficher correctement""" self.timer.stop() self.appliquer() def appliquer(self): """Lancement de la fonction chang_format""" self.chang_format() def appliquer0(self): """Préparation de la conversion""" suffix="" # Boîte de dialogue pour sauvegarder (nom du nouveau fichier) self.chemDossierSauv = EkdSaveDialog(self, mode="image", suffix=suffix, title=_(u"Sauver"), multiple=True) self.chemDossierSauv = self.chemDossierSauv.getFile() if not self.chemDossierSauv: return self.progress.reset() self.progress.show() self.progress.setValue(0) QApplication.processEvents() # Lancement de la conversion dans 250 ms (seule solution trouvée pour éviter # le grisage au début) self.timer.start(250) def afficherAide(self): """Boîte de dialogue de l'aide""" # ATTENTION _ a été mis à la place de tr car le script de mise à jour gettext (mise_a_jour_gettext.py) # ne fonctionne pas pour ekdDoc.pot avec les clés --keyword donc les nouvelles lignes vides encore # non traduites de la doc se retrouveront dans ekd.pot au lieu de ekdDoc.pot # Utilisation de EkdAide messageAide=EkdAide(parent=self) messageAide.setText(_(u"<p><b>Vous pouvez ici changer/transformer le format des images (et par là même en changer leur extension). En ce qui concerne le JPEG, vous pourrez sélectionner deux types d'extension (jpeg ou jpg), toujours pour le JPEG, vous aurez aussi la possiblité de régler la qualité (c'est à dire la compression).</b></p><p><b>Les formats pris en compte sont: JPEG, PNG, GIF, BMP, PPM, TIFF et TIF.</b></p><p>Dans l'onglet <b>'Images sources'</b> cliquez sur le bouton <b>Ajouter</b>, une boîte de dialogue apparaît, sur la partie gauche sélectionnez le répertoire (au besoin dépliez les sous-répertoires), allez chercher vos image(s). Si vous voulez sélectionner plusieurs images d'un coup, maintenez la touche <b>CTRL</b> (ou <b>SHIFT</b>) du clavier enfoncée (tout en sélectionnant vos images), cliquez sur <b>Ajouter</b>.</p><p>Dans l'onglet <b>'Réglages'</b> faites les réglages du <b>'Traitement à partir de l'image (numéro)'</b> et du <b>'Nombre de chiffres après le nom de l'image' <font color='red'>(la plupart du temps les valeurs par défaut suffisent)</b></font>, ensuite choisissez votre <b>'Type de format après traitement'</b> et réglez la <b>'Qualité'</b> (disponible uniquement si vous avez sélectionné une image JPEG en sortie). Cliquez sur le bouton <b>'Voir le résultat'</b> (vous voyez à ce moment le résultat de vos réglages sur la première image du lot s'afficher dans une nouvelle fenêtre).</p></p><p>Une fois tout ceci fait, cliquez sur le bouton <b>'Appliquer'</b>, sélectionnez le répertoire de sauvegarde, indiquez votre <b>'Nom de fichier'</b>, cliquez sur le bouton <b>'Enregistrer'</b>.</p><p>Si vous faites un clic droit de la souris (sur l'image) dans l'onglet <b>Image(s) après traitement</b>, vous accédez à des paramètres vous permettant différents affichages de la dite image. De même dans cet onglet vous pouvez lancer la visualisation des images par le bouton <b>Lancer le diaporama</b> (le bouton violet avec une flèche blanche vers la droite).</p><p>L'onglet <b>'Infos'</b> vous permet de voir le filtre utilisé, les image(s) chargée(s) et les image(s) convertie(s).</p>")) messageAide.show() def save(self) : self.afficheurImgSource.saveFileLocation(self.idSection) EkdConfig.set(self.idSection, u'choixReglage', unicode(self.comboReglage.currentIndex())) EkdConfig.set(self.idSection, u'spin1', unicode(self.spin1.value())) EkdConfig.set(self.idSection, u'spin2', unicode(self.spin2.value())) EkdConfig.set(self.idSection, u'spin3', unicode(self.spin3.value())) def load(self) : self.afficheurImgSource.loadFileLocation(self.idSection) self.comboReglage.setCurrentIndex(int(EkdConfig.get(self.idSection, u'choixReglage'))) self.spin1.setValue(int(EkdConfig.get(self.idSection, u'spin1'))) self.spin2.setValue(int(EkdConfig.get(self.idSection, u'spin2'))) self.spin3.setValue(int(EkdConfig.get(self.idSection, u'spin3')))
def __init__(self, statusBar, geometry): QWidget.__init__(self) # ---------------------------- # Quelques paramètres de base # ---------------------------- # Paramètres de configuration self.config = EkdConfig # Fonctions communes à plusieurs cadres du module Image self.base = Base() self.idSection = "image_image_composite" # Log du terminal self.base.printSection(self.idSection) # Fonction appelant la fenêtre principale self.mainWindowFrameGeometry = geometry # Création des répertoires temporaires. Utilisation de EkdConfig self.repTampon = EkdConfig.getTempDir() + os.sep + "tampon" + os.sep + "temp_duplication" + os.sep if os.path.isdir(self.repTampon) is False: os.makedirs(self.repTampon) # Au cas où le répertoire existait déjà et qu'il n'était pas vide # -> purge (simple précausion) for toutRepCompo in glob.glob(self.repTampon+'*.*'): os.remove(toutRepCompo) # Répertoire tampon dans lequel est crée le compositing qui sera crée par le # bouton 'Voir le résultat'. Utilisation de EkdConfig self.repTamponVisuVoirRes = self.repTampon + "visu_voir_res_compo" + os.sep if os.path.isdir(self.repTamponVisuVoirRes) is False: os.makedirs(self.repTamponVisuVoirRes) # Au cas où le répertoire existait déjà et qu'il n'était pas vide # -> purge (simple précausion) for toutRepCompoVisu in glob.glob(self.repTamponVisuVoirRes+'*.*'): os.remove(toutRepCompoVisu) # Répertoire temporaire 1 pour les redimensionnement des images if os.path.isdir(self.repTampon+'redim_1/') is False: os.makedirs(self.repTampon+'redim_1/') # Répertoire temporaire 2 pour les redimensionnement des images if os.path.isdir(self.repTampon+'redim_2/') is False: os.makedirs(self.repTampon+'redim_2/') # Au cas où le répertoire existait déjà et qu'il n'était pas vide # -> purge (simple précausion) for toutRepCompoRedim in glob.glob(self.repTampon+'redim_1/'+'*.*'): if len(toutRepCompoRedim)>0: os.remove(toutRepCompoRedim) # ... for toutRepCompoRedim in glob.glob(self.repTampon+'redim_2/'+'*.*'): if len(toutRepCompoRedim)>0: os.remove(toutRepCompoRedim) #=== Drapeaux ===# # Une conversion (même partielle) a-t-elle eu lieu après le chargement des images? (1: vrai) # Est-ce que des images ont été converties et qu'elles n'ont pas encore été montrées? # Marche aussi quand la conversion a été arrêté avant la fin de la 1ère image self.conversionImg = 0 # Est-ce qu'une prévisualisation a été appelée? self.previsualImg = 0 # Est-ce que des images sources ont été modifiées? (c'est-à-dire ajoutées ou supprimées) self.modifImageSource = 0 # Liste de chemins de fichiers avec et sans canal alpha et du dossier de sauvegarde self.listeChemAVcanAlph=[] self.listeChemSANScanAlph=[] self.listeImgDestin = [] # Boite d'alignement vertical vbox=QVBoxLayout(self) # -------------------------------------------------- # widgets du haut : titre + bouton de sélection # -------------------------------------------------- hbox = QHBoxLayout() # Ajout du titre de la page et de l'aperçu à la boite verticale vbox.addLayout(hbox, 0) #=== Bouton de sélection des images alpha et sans alpha ===# hbox = QHBoxLayout() self.framReglage=QFrame() vboxReglage=QVBoxLayout(self.framReglage) # Pour la gestion du nombre d'images à traiter ############## self.grid = QGridLayout() self.grid.addWidget(QLabel(_(u"Traitement à partir de l'image (numéro)")), 0, 0) self.spin1=SpinSlider(1, 100000, 1, '', self) self.grid.addWidget(self.spin1, 0, 1) self.connect(self.spin1, SIGNAL("valueChanged(int)"), self.changeValNbreImg_1) self.grid.addWidget(QLabel(_(u"Nombre de chiffres après le nom de l'image")), 1, 0) self.spin2=SpinSlider(3, 18, 6, '', self) self.grid.addWidget(self.spin2, 1, 1) self.connect(self.spin2, SIGNAL("valueChanged(int)"), self.changeValNbreImg_1) self.grid.setAlignment(Qt.AlignHCenter) vboxReglage.addLayout(self.grid) vboxReglage.addStretch() # ------------------------------------------------- # Onglets d'affichage image source et destination # ------------------------------------------------- # On peut sélectionner les extensions qui doivent être visibles comme ceci: #self.afficheurImgSource=SelectWidget(extensions=["*.jpg", "*.png"], geometrie = self.mainWindowFrameGeometry) # Là uniquement les fichiers png et gif apparaissent ds la fenêtre de chargement # Ne pas oublier de mettre * avant le point et l'extension # Là où s'afficheront les images # Avec canal alpha self.afficheurImgSourceAvecCanalAlpha=SelectWidget(extensions=["*.png", "*.gif"], geometrie = self.mainWindowFrameGeometry) # Sans canal alpha self.afficheurImgSourceSansCanalAlpha=SelectWidget(geometrie = self.mainWindowFrameGeometry) # Gestion de la configuration via EkdConfig # Résultat du compositing self.afficheurImgDestination=Lecture_VisionImage(statusBar) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "image" # Défini le type de fichier source. self.typeSortie = "image" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurImgSourceSansCanalAlpha # Fait le lien avec le sélecteur de fichier source. ### Remarque : Le choix a été fait de ne pas mettre la boîte de sélection des images alpha dans le tampon. # infos - logs self.zoneAffichInfosImg = QTextEdit("") if PYQT_VERSION_STR < "4.1.0": self.zoneAffichInfosImg.setText = self.zoneAffichInfosImg.setPlainText self.zoneAffichInfosImg.setReadOnly(True) self.fram=QFrame() vboxInfIm=QVBoxLayout(self.fram) vboxInfIm.addWidget(self.zoneAffichInfosImg) self.fram.setEnabled(False) self.tabwidget=QTabWidget() self.indexTabImgSourceAvCanAlph = self.tabwidget.addTab(self.afficheurImgSourceAvecCanalAlpha, _(u'Image(s) avec canal alpha')) self.indexTabImgSourceSansCanAlph = self.tabwidget.addTab(self.afficheurImgSourceSansCanalAlpha, _(u'Image(s) sans canal alpha')) self.indexTabReglage=self.tabwidget.addTab(self.framReglage, _(u'Réglages')) self.indexTabImgDestin=self.tabwidget.addTab(self.afficheurImgDestination, _(u'Image(s) après traitement')) self.indexTabInfo=self.tabwidget.addTab(self.fram, _(u'Infos')) vbox.addWidget(self.tabwidget) # ------------------------------------------------------------------- # widgets du bas : ligne + boutons # ------------------------------------------------------------------- # boutons boutAide=QPushButton(_(u" Aide")) boutAide.setIcon(QIcon("Icones/icone_aide_128.png")) self.connect(boutAide, SIGNAL("clicked()"), self.afficherAide) self.boutApPremImg = QPushButton(_(u" Voir le résultat")) self.boutApPremImg.setIcon(QIcon("Icones/icone_visionner_128.png")) self.boutApPremImg.setFocusPolicy(Qt.NoFocus) self.boutApPremImg.setEnabled(False) self.connect(self.boutApPremImg, SIGNAL("clicked()"), self.visu_1ere_derniere_img) self.boutAppliquer=QPushButton(_(u" Appliquer et sauver")) self.boutAppliquer.setIcon(QIcon("Icones/icone_appliquer_128.png")) self.boutAppliquer.setEnabled(False) self.connect(self.boutAppliquer, SIGNAL("clicked()"), self.appliquer) # ligne de séparation juste au dessus des boutons ligne = QFrame() ligne.setFrameShape(QFrame.HLine) ligne.setFrameShadow(QFrame.Sunken) vbox.addWidget(ligne) vbox.addSpacing(-5) # la ligne doit être plus près des boutons hbox=QHBoxLayout() hbox.addWidget(boutAide) hbox.addStretch() # espace entre les 2 boutons hbox.addWidget(self.boutApPremImg) hbox.addStretch() hbox.addWidget(self.boutAppliquer) vbox.addLayout(hbox) # affichage de la boîte principale self.setLayout(vbox) self.connect(self.tabwidget, SIGNAL("currentChanged(int)"), self.fctTab) #---------------------------------------------------------------------------------------------------- # Signal de présence d'images dans ler widget de sélection -> modifie le statut des boutons d'action #---------------------------------------------------------------------------------------------------- self.connect(self.afficheurImgSourceSansCanalAlpha, SIGNAL("pictureChanged(int)"), self.modifBoutonsAction)
class Image_Divers_Compositing(QWidget): # ----------------------------------- # Cadre accueillant les widgets de : # Image >> Divers >> Compositing # ----------------------------------- def __init__(self, statusBar, geometry): QWidget.__init__(self) # ---------------------------- # Quelques paramètres de base # ---------------------------- # Paramètres de configuration self.config = EkdConfig # Fonctions communes à plusieurs cadres du module Image self.base = Base() self.idSection = "image_image_composite" # Log du terminal self.base.printSection(self.idSection) # Fonction appelant la fenêtre principale self.mainWindowFrameGeometry = geometry # Création des répertoires temporaires. Utilisation de EkdConfig self.repTampon = EkdConfig.getTempDir() + os.sep + "tampon" + os.sep + "temp_duplication" + os.sep if os.path.isdir(self.repTampon) is False: os.makedirs(self.repTampon) # Au cas où le répertoire existait déjà et qu'il n'était pas vide # -> purge (simple précausion) for toutRepCompo in glob.glob(self.repTampon+'*.*'): os.remove(toutRepCompo) # Répertoire tampon dans lequel est crée le compositing qui sera crée par le # bouton 'Voir le résultat'. Utilisation de EkdConfig self.repTamponVisuVoirRes = self.repTampon + "visu_voir_res_compo" + os.sep if os.path.isdir(self.repTamponVisuVoirRes) is False: os.makedirs(self.repTamponVisuVoirRes) # Au cas où le répertoire existait déjà et qu'il n'était pas vide # -> purge (simple précausion) for toutRepCompoVisu in glob.glob(self.repTamponVisuVoirRes+'*.*'): os.remove(toutRepCompoVisu) # Répertoire temporaire 1 pour les redimensionnement des images if os.path.isdir(self.repTampon+'redim_1/') is False: os.makedirs(self.repTampon+'redim_1/') # Répertoire temporaire 2 pour les redimensionnement des images if os.path.isdir(self.repTampon+'redim_2/') is False: os.makedirs(self.repTampon+'redim_2/') # Au cas où le répertoire existait déjà et qu'il n'était pas vide # -> purge (simple précausion) for toutRepCompoRedim in glob.glob(self.repTampon+'redim_1/'+'*.*'): if len(toutRepCompoRedim)>0: os.remove(toutRepCompoRedim) # ... for toutRepCompoRedim in glob.glob(self.repTampon+'redim_2/'+'*.*'): if len(toutRepCompoRedim)>0: os.remove(toutRepCompoRedim) #=== Drapeaux ===# # Une conversion (même partielle) a-t-elle eu lieu après le chargement des images? (1: vrai) # Est-ce que des images ont été converties et qu'elles n'ont pas encore été montrées? # Marche aussi quand la conversion a été arrêté avant la fin de la 1ère image self.conversionImg = 0 # Est-ce qu'une prévisualisation a été appelée? self.previsualImg = 0 # Est-ce que des images sources ont été modifiées? (c'est-à-dire ajoutées ou supprimées) self.modifImageSource = 0 # Liste de chemins de fichiers avec et sans canal alpha et du dossier de sauvegarde self.listeChemAVcanAlph=[] self.listeChemSANScanAlph=[] self.listeImgDestin = [] # Boite d'alignement vertical vbox=QVBoxLayout(self) # -------------------------------------------------- # widgets du haut : titre + bouton de sélection # -------------------------------------------------- hbox = QHBoxLayout() # Ajout du titre de la page et de l'aperçu à la boite verticale vbox.addLayout(hbox, 0) #=== Bouton de sélection des images alpha et sans alpha ===# hbox = QHBoxLayout() self.framReglage=QFrame() vboxReglage=QVBoxLayout(self.framReglage) # Pour la gestion du nombre d'images à traiter ############## self.grid = QGridLayout() self.grid.addWidget(QLabel(_(u"Traitement à partir de l'image (numéro)")), 0, 0) self.spin1=SpinSlider(1, 100000, 1, '', self) self.grid.addWidget(self.spin1, 0, 1) self.connect(self.spin1, SIGNAL("valueChanged(int)"), self.changeValNbreImg_1) self.grid.addWidget(QLabel(_(u"Nombre de chiffres après le nom de l'image")), 1, 0) self.spin2=SpinSlider(3, 18, 6, '', self) self.grid.addWidget(self.spin2, 1, 1) self.connect(self.spin2, SIGNAL("valueChanged(int)"), self.changeValNbreImg_1) self.grid.setAlignment(Qt.AlignHCenter) vboxReglage.addLayout(self.grid) vboxReglage.addStretch() # ------------------------------------------------- # Onglets d'affichage image source et destination # ------------------------------------------------- # On peut sélectionner les extensions qui doivent être visibles comme ceci: #self.afficheurImgSource=SelectWidget(extensions=["*.jpg", "*.png"], geometrie = self.mainWindowFrameGeometry) # Là uniquement les fichiers png et gif apparaissent ds la fenêtre de chargement # Ne pas oublier de mettre * avant le point et l'extension # Là où s'afficheront les images # Avec canal alpha self.afficheurImgSourceAvecCanalAlpha=SelectWidget(extensions=["*.png", "*.gif"], geometrie = self.mainWindowFrameGeometry) # Sans canal alpha self.afficheurImgSourceSansCanalAlpha=SelectWidget(geometrie = self.mainWindowFrameGeometry) # Gestion de la configuration via EkdConfig # Résultat du compositing self.afficheurImgDestination=Lecture_VisionImage(statusBar) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "image" # Défini le type de fichier source. self.typeSortie = "image" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurImgSourceSansCanalAlpha # Fait le lien avec le sélecteur de fichier source. ### Remarque : Le choix a été fait de ne pas mettre la boîte de sélection des images alpha dans le tampon. # infos - logs self.zoneAffichInfosImg = QTextEdit("") if PYQT_VERSION_STR < "4.1.0": self.zoneAffichInfosImg.setText = self.zoneAffichInfosImg.setPlainText self.zoneAffichInfosImg.setReadOnly(True) self.fram=QFrame() vboxInfIm=QVBoxLayout(self.fram) vboxInfIm.addWidget(self.zoneAffichInfosImg) self.fram.setEnabled(False) self.tabwidget=QTabWidget() self.indexTabImgSourceAvCanAlph = self.tabwidget.addTab(self.afficheurImgSourceAvecCanalAlpha, _(u'Image(s) avec canal alpha')) self.indexTabImgSourceSansCanAlph = self.tabwidget.addTab(self.afficheurImgSourceSansCanalAlpha, _(u'Image(s) sans canal alpha')) self.indexTabReglage=self.tabwidget.addTab(self.framReglage, _(u'Réglages')) self.indexTabImgDestin=self.tabwidget.addTab(self.afficheurImgDestination, _(u'Image(s) après traitement')) self.indexTabInfo=self.tabwidget.addTab(self.fram, _(u'Infos')) vbox.addWidget(self.tabwidget) # ------------------------------------------------------------------- # widgets du bas : ligne + boutons # ------------------------------------------------------------------- # boutons boutAide=QPushButton(_(u" Aide")) boutAide.setIcon(QIcon("Icones/icone_aide_128.png")) self.connect(boutAide, SIGNAL("clicked()"), self.afficherAide) self.boutApPremImg = QPushButton(_(u" Voir le résultat")) self.boutApPremImg.setIcon(QIcon("Icones/icone_visionner_128.png")) self.boutApPremImg.setFocusPolicy(Qt.NoFocus) self.boutApPremImg.setEnabled(False) self.connect(self.boutApPremImg, SIGNAL("clicked()"), self.visu_1ere_derniere_img) self.boutAppliquer=QPushButton(_(u" Appliquer et sauver")) self.boutAppliquer.setIcon(QIcon("Icones/icone_appliquer_128.png")) self.boutAppliquer.setEnabled(False) self.connect(self.boutAppliquer, SIGNAL("clicked()"), self.appliquer) # ligne de séparation juste au dessus des boutons ligne = QFrame() ligne.setFrameShape(QFrame.HLine) ligne.setFrameShadow(QFrame.Sunken) vbox.addWidget(ligne) vbox.addSpacing(-5) # la ligne doit être plus près des boutons hbox=QHBoxLayout() hbox.addWidget(boutAide) hbox.addStretch() # espace entre les 2 boutons hbox.addWidget(self.boutApPremImg) hbox.addStretch() hbox.addWidget(self.boutAppliquer) vbox.addLayout(hbox) # affichage de la boîte principale self.setLayout(vbox) self.connect(self.tabwidget, SIGNAL("currentChanged(int)"), self.fctTab) #---------------------------------------------------------------------------------------------------- # Signal de présence d'images dans ler widget de sélection -> modifie le statut des boutons d'action #---------------------------------------------------------------------------------------------------- self.connect(self.afficheurImgSourceSansCanalAlpha, SIGNAL("pictureChanged(int)"), self.modifBoutonsAction) def modifBoutonsAction(self, i): "On active ou désactive les boutons d'action selon s'il y a des images ou pas dans le widget de sélection" self.boutAppliquer.setEnabled(i) self.boutApPremImg.setEnabled(i) self.modifImageSource = 1 def changeValNbreImg_1(self): """Gestion du nombre d'images à traiter""" #print "Traitement a partir de l'image (numero):", self.spin1.value() EkdPrint(u"Traitement a partir de l'image (numero): %s" % self.spin1.value()) #print "Nombre de chiffres apres le nom de l'image:", self.spin2.value() EkdPrint(u"Nombre de chiffres apres le nom de l'image: %s" % self.spin2.value()) def fctTab(self, i): "Affichage d'une ou plusieurs images converties" # Cela ne concerne que l'onglet de visualisation des images après leur conversion if i == self.indexTabImgDestin: if self.conversionImg: # Affichage si on sauvegarde par le bouton Appliquer et sauver #print "La conversion vient d'avoir lieu -> affichage des images du lot de destination" EkdPrint(u"La conversion vient d'avoir lieu -> affichage des images du lot de destination") cheminImages = os.path.dirname(self.listeImgDestin[0]) liste = [] for fichier in self.listeImgDestin: liste.append(os.path.basename(fichier)) self.afficheurImgDestination.updateImages(liste, cheminImages) elif not self.boutAppliquer.isEnabled() or self.modifImageSource: # Si le bouton de conversion n'est pas actif, c'est qu'il n'y a plus d'image source # -> on n'a plus de raison de maintenir des images dans l'afficheur de résultat # Si les images sources ont été modifiées, on purge aussi l'afficheur de résultat self.afficheurImgDestination.updateImages([]) self.conversionImg = 0 self.modifImageSource = 0 def metaFctTab(self, i): """Changement d'onglet (conçu pour sélectionner les onglets "Images Source" après le chargement de nouvelles images sources ou "Images Après Traitement" après la conversion). But: s'assurer que la fonction associée au QTabWidget (affichage d'images, grisage/dégrisage du curseur...) sera bien appliquée même si on est déjà sur le bon onglet""" if self.tabwidget.currentIndex()!=i: self.tabwidget.setCurrentIndex(i) else: self.fctTab(i) def stat_dim_img_1(self): """Calcul statistique des dimensions des images les plus présentes dans le lot""" # Récupération de la liste des fichiers chargés (avec canal alpha) self.listeChemAVcanAlph=self.afficheurImgSourceAvecCanalAlpha.getFiles() # Ouverture et mise ds une liste des dimensions des images listePrepaRedim=[Image.open(aA).size for aA in self.listeChemAVcanAlph] # Merci beaucoup à Marc Keller de la liste: python at aful.org de m'avoir # aidé pour cette partie (les 4 lignes en dessous) dictSeq={}.fromkeys(listePrepaRedim, 0) for cle in listePrepaRedim: dictSeq[cle]+=1 self.lStatDimSeq=sorted(zip(dictSeq.itervalues(), dictSeq.iterkeys()), reverse=1) self.dimStatImg=self.lStatDimSeq[0][1] #print "Toutes les dimensions des images (avec le nbre d'images):", self.lStatDimSeq EkdPrint(u"Toutes les dimensions des images (avec le nbre d'images): " + str(self.lStatDimSeq)) #print 'Dimension des images la plus presente dans la sequence:', self.dimStatImg EkdPrint(u'Dimension des images la plus presente dans la sequence: ' + str(self.dimStatImg)) #print "Nombre de tailles d'images différentes dans le lot :", len(self.lStatDimSeq) EkdPrint(u"Nombre de tailles d'images différentes dans le lot: " + str(len(self.lStatDimSeq))) if len(self.lStatDimSeq)>1: return 0 else: return 1 def redim_img_1(self): """Si l'utilisateur charge des images avec des tailles complètement différentes --> les images de la séquence peuvent être redimensionnées""" if not self.stat_dim_img_1(): reply = QMessageBox.warning(self, 'Message', _(u"Vos images ne sont pas toutes de la même taille. Voulez-vous redimensionner les images de sortie à la taille la plus répandue dans la séquence ?. Dans la plupart des cas il faut répondre oui."), QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.No: return # Les images de tailles différentes à la plus répandue sont redimensionnées # dans un répertoire temporaire. # Les images redimensionnées voient leur chemin modifié dans la liste des # chemins des images sources. Les autres chemins ne changent pas. index=0 for chemImg in self.listeChemAVcanAlph: obImg=Image.open(chemImg) if obImg.size!=self.dimStatImg: pass sRedim=obImg.resize(self.dimStatImg, Image.ANTIALIAS) chemSortie = self.repTampon+'redim_1'+os.sep+os.path.basename(chemImg) sRedim.save(chemSortie) self.listeChemAVcanAlph[index] = chemSortie index += 1 def stat_dim_img_2(self): """Calcul statistique des dimensions des images les plus présentes dans le lot""" # Récupération de la liste des fichiers chargés (sans canal alpha) self.listeChemSANScanAlph=self.afficheurImgSourceSansCanalAlpha.getFiles() ########################################################################### # Ouverture et mise ds une liste des dimensions des images listePrepaRedim=[Image.open(aA).size for aA in self.listeChemSANScanAlph] # Merci beaucoup à Marc Keller de la liste: python at aful.org de m'avoir # aidé pour cette partie (les 4 lignes en dessous) dictSeq={}.fromkeys(listePrepaRedim, 0) for cle in listePrepaRedim: dictSeq[cle]+=1 self.lStatDimSeq=sorted(zip(dictSeq.itervalues(), dictSeq.iterkeys()), reverse=1) self.dimStatImg=self.lStatDimSeq[0][1] #print self.dimStatImg EkdPrint(self.dimStatImg) #print "Toutes les dimensions des images (avec le nbre d'images):", self.lStatDimSeq EkdPrint(u"Toutes les dimensions des images (avec le nbre d'images): " + str(self.lStatDimSeq)) #print 'Dimension des images la plus presente dans la sequence:', self.dimStatImg EkdPrint(u'Dimension des images la plus presente dans la sequence: ' + str(self.dimStatImg)) #print "Nombre de tailles d'images différentes dans le lot :", len(self.lStatDimSeq) EkdPrint(u"Nombre de tailles d'images différentes dans le lot: " + str(len(self.lStatDimSeq))) if len(self.lStatDimSeq)>1: return 0 else: return 1 def redim_img_2(self): """Si l'utilisateur charge des images avec des tailles complètement différentes --> les images de la séquence peuvent être redimensionnées""" if not self.stat_dim_img_2(): reply = QMessageBox.warning(self, 'Message', _(u"Vos images ne sont pas toutes de la même taille. Voulez-vous redimensionner les images de sortie à la taille la plus répandue dans la séquence ?. Dans la plupart des cas il faut répondre oui."), QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.No: return # Les images de tailles différentes à la plus répandue sont redimensionnées # dans un répertoire temporaire. # Les images redimensionnées voient leur chemin modifié dans la liste des # chemins des images sources. Les autres chemins ne changent pas. index=0 for chemImg in self.listeChemSANScanAlph: obImg=Image.open(chemImg) if obImg.size!=self.dimStatImg: pass sRedim=obImg.resize(self.dimStatImg, Image.ANTIALIAS) chemSortie = self.repTampon+'redim_2'+os.sep+os.path.basename(chemImg) sRedim.save(chemSortie) self.listeChemSANScanAlph[index] = chemSortie index += 1 def visu_1ere_derniere_img(self): """Visionnement du compositing avant application""" # Si l'utilisateur charge des images de taille différente, fait le traitement # ..., recharge de nouvelles images, il faut que les répertoires de redimen- # sionnement soient vidés listePresRedim_1=glob.glob(self.repTampon+'redim_1'+os.sep+'*.*') listePresRedim_1.sort() if len(listePresRedim_1)>0: for parcR_1 in listePresRedim_1: os.remove(parcR_1) listePresRedim_2=glob.glob(self.repTampon+'redim_2'+os.sep+'*.*') listePresRedim_2.sort() if len(listePresRedim_2)>0: for parcR_2 in listePresRedim_2: os.remove(parcR_2) # Récupération de la liste des fichiers chargés (avec canal alpha) self.listeChemAVcanAlph=self.afficheurImgSourceAvecCanalAlpha.getFiles() self.listeChemAVcanAlph.sort() # Récupération de la liste des fichiers chargés (sans canal alpha) self.listeChemSANScanAlph=self.afficheurImgSourceSansCanalAlpha.getFiles() self.listeChemSANScanAlph.sort() # Vérification du fait que les fichiers avec canal alpha chargés contiennent bien # un canal alpha (RGBA) ... sinon affichage d'une boîte de dialogue d'erreur et arrêt # du traitement des images for parcMode in self.listeChemAVcanAlph: imVerifCanAlph=Image.open(parcMode) if imVerifCanAlph.mode!='RGBA': messErr=QMessageBox(self) messErr.setText(_(u"<b>Vous avez chargé des images sans canal alpha</b> (c'est à dire sans transparence) et ce à partir de l'onglet <b>Image(s) avec canal alpha</b>. Sans transparence, vous ne pouvez, en aucun cas, appliquer un compositing (vos images doivent être en mode RGBA pour que cela réussisse) !.")) messErr.setWindowTitle(_(u"Erreur")) messErr.setIcon(QMessageBox.Critical) messErr.exec_() return # ----- TRAVAIL PREPARATOIRE --- Redimensionnement des images ----------------- try: nbreElem_1=len(self.listeChemAVcanAlph) nbreElem_2=len(self.listeChemSANScanAlph) # Appel des fonction de redimensionnement self.redim_img_1() self.redim_img_2() # Récup des listes contenant les fichiers repRedimTemp_1=glob.glob(self.repTampon+'redim_1'+os.sep+'*.*') repRedimTemp_1.sort() repRedimTemp_2=glob.glob(self.repTampon+'redim_2'+os.sep+'*.*') repRedimTemp_2.sort() except: messageErreur=QMessageBox(self) messageErreur.setText(_(u"<p>Vous n'avez pas chargé d'image(s) (bouton Ajouter) dans l'onglet <b>Image(s) avec canal aplpha</b>. Recommencez et chargez des images aussi bien dans <b>Image(s) avec canal aplpha</b>, que dans <b>Image(s) sans canal aplpha</b>.</p>")) messageErreur.setWindowTitle(_(u"Erreur")) messageErreur.setIcon(QMessageBox.Critical) messageErreur.exec_() return # ----------------------------------------------------------------------------- try: # tImgAVcanAlph --> taille des images avec canal alpha # tImgSANScanAlph --> taille des images avec canal alpha # =/= --> différent # == --> strictement ègal # Si tImgAVcanAlph == entre elles et tImgSANScanAlph == entre elles # mais tImgAVcanAlph =/= tImgSANScanAlph (tImgAVcanAlph == tImgSANScanAlph # est aussi valable) if len(repRedimTemp_1)==0 and len(repRedimTemp_2)==0: im01=Image.open(self.listeChemAVcanAlph[nbreElem_1-1]) im02=Image.open(self.listeChemSANScanAlph[0]) # Redimensionnement à la tImgAVcanAlph im02=im02.resize(Image.open(self.listeChemAVcanAlph[nbreElem_1-1]).size, Image.ANTIALIAS) imgCompoUndeChaque=Image.composite(im01, im02, im01) # Si tImgAVcanAlph == entre elles et tImgSANScanAlph =/= entre elles elif len(repRedimTemp_1)==0 and len(repRedimTemp_2)>1: im01=Image.open(self.listeChemAVcanAlph[nbreElem_1-1]) im02=Image.open(repRedimTemp_2[0]) # Redimensionnement à la tImgAVcanAlph im02=im02.resize(Image.open(self.listeChemAVcanAlph[nbreElem_1-1]).size, Image.ANTIALIAS) imgCompoUndeChaque=Image.composite(im01, im02, im01) # Si tImgAVcanAlph =/= entre elles et tImgSANScanAlph =/= entre elles # Plusieurs images avec can alpha et plusieurs images sans can alpha elif len(repRedimTemp_1)>1 and len(repRedimTemp_2)>1: im01=Image.open(repRedimTemp_1[nbreElem_1-1]) im02=Image.open(repRedimTemp_2[0]) # Redimensionnement à la tImgAVcanAlph im02=im02.resize(Image.open(repRedimTemp_1[nbreElem_1-1]).size, Image.ANTIALIAS) imgCompoUndeChaque=Image.composite(im01, im02, im01) # Si tImgAVcanAlph =/= entre elles et tImgSANScanAlph == ... là on a # qu'une image sans canal alpha ce qui correspond à un arrière plan fixe elif len(repRedimTemp_1)>1 and len(repRedimTemp_2)==0: im01=Image.open(repRedimTemp_1[nbreElem_1-1]) im02=Image.open(self.listeChemSANScanAlph[0]) # Redimensionnement à la tImgAVcanAlph im02=im02.resize(Image.open(repRedimTemp_1[nbreElem_1-1]).size, Image.ANTIALIAS) imgCompoUndeChaque=Image.composite(im01, im02, im01) except: messageErreur=QMessageBox(self) messageErreur.setText(_(u"<p><b>Première situation d'erreur:</b> Vous n'avez pas chargé d'image(s) (bouton Ajouter) dans l'onglet <b>Image(s) avec canal aplpha</b>. Recommencez et chargez des images aussi bien dans <b>Image(s) avec canal aplpha</b>, que dans <b>Image(s) sans canal aplpha</b>.</p><p><b>Seconde situation d'erreur:</b> la visualisation de l'image ne peut pas avoir lieu car vous avez répondu non au moins une fois au moment du redimensionnement des images. Recommencez et répondez oui aux deux boîtes de dialogue.</p>")) messageErreur.setWindowTitle(_(u"Erreur")) messageErreur.setIcon(QMessageBox.Critical) messageErreur.exec_() return # Sauvegarde des images resultant du Compositing self.cheminCourantSauv = self.repTamponVisuVoirRes+'visu_compositing_'+string.zfill((1), 6)+'.png' imgCompoUndeChaque.save(self.cheminCourantSauv, "PNG") # Affichage de l'image temporaire # Ouverture d'une boite de dialogue affichant l'aperçu. # # Affichage par le bouton Voir le résultat visio = VisionneurEvolue(self.cheminCourantSauv) visio.redimenFenetre(self.mainWindowFrameGeometry, 1., 0.7) visio.exec_() return 0 def appliquer(self): """Appliquer le compositing""" # Si l'utilisateur charge des images de taille différente, fait le traitement # ..., recharge de nouvelles images, il faut que les répertoires de redimen- # sionnement soient vidés listePresRedim_1=glob.glob(self.repTampon+'redim_1'+os.sep+'*.*') listePresRedim_1.sort() if len(listePresRedim_1)>0: for parcR_1 in listePresRedim_1: os.remove(parcR_1) listePresRedim_2=glob.glob(self.repTampon+'redim_2'+os.sep+'*.*') listePresRedim_2.sort() if len(listePresRedim_2)>0: for parcR_2 in listePresRedim_2: os.remove(parcR_2) # La liste pour l'affichage des images ds l'interface est # vidée pour que les images affichées ne s'amoncellent pas # si plusieurs rendus à la suite self.listeImgDestin=[] # Récupération de la liste des fichiers chargés (avec canal alpha) self.listeChemAVcanAlph=self.afficheurImgSourceAvecCanalAlpha.getFiles() self.listeChemAVcanAlph.sort() # Récupération de la liste des fichiers chargés (sans canal alpha) self.listeChemSANScanAlph=self.afficheurImgSourceSansCanalAlpha.getFiles() self.listeChemSANScanAlph.sort() # Vérification du fait que les fichiers avec canal alpha chargés contiennent bien # un canal alpha (RGBA) ... sinon affichage d'une boîte de dialogue d'erreur et arrêt # du traitement des images for parcMode in self.listeChemAVcanAlph: imVerifCanAlph=Image.open(parcMode) if imVerifCanAlph.mode!='RGBA': messErr=QMessageBox(self) messErr.setText(_(u"<b>Vous avez chargé des images sans canal alpha</b> (c'est à dire sans transparence) et ce à partir de l'onglet <b>Image(s) avec canal alpha</b>. Sans transparence, vous ne pouvez, en aucun cas, appliquer un compositing (vos images doivent être en mode RGBA pour que cela réussisse) !.")) messErr.setWindowTitle(_(u"Erreur")) messErr.setIcon(QMessageBox.Critical) messErr.exec_() return # ----- TRAVAIL PREPARATOIRE --- Redimensionnement des images ----------------- try: nbreElem_1=len(self.listeChemAVcanAlph) nbreElem_2=len(self.listeChemSANScanAlph) # Appel des fonction de redimensionnement self.redim_img_1() self.redim_img_2() # Récup des listes contenant les fichiers ### Le 18/09/09 ## ...+'redim_1/*.*' transformé en ...+'redim_1'+os.sep+'*.*' repRedimTemp_1=glob.glob(self.repTampon+'redim_1'+os.sep+'*.*') repRedimTemp_1.sort() ### Le 18/09/09 ## ...+'redim_2/*.*' transformé en ...+'redim_2'+os.sep+'*.*' repRedimTemp_2=glob.glob(self.repTampon+'redim_2'+os.sep+'*.*') repRedimTemp_2.sort() except: messageErreur=QMessageBox(self) messageErreur.setText(_(u"<p>Vous n'avez pas chargé d'image(s) (bouton Ajouter) dans l'onglet <b>Image(s) avec canal aplpha</b>. Recommencez et chargez des images aussi bien dans <b>Image(s) avec canal aplpha</b>, que dans <b>Image(s) sans canal aplpha</b>.</p>")) messageErreur.setWindowTitle(_(u"Erreur")) messageErreur.setIcon(QMessageBox.Critical) messageErreur.exec_() return # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # Boîte de dialogue pour sauvegarder (nom du nouveau fichier) #------------------------------------------------------------------------------ # Utilisation de la nouvelle boîte de dialogue de sauvegarde suffix="" self.chemDossierSauv = EkdSaveDialog(self, mode="image", suffix=suffix, title=_(u"Sauver"), multiple=True) self.chemDossierSauv = self.chemDossierSauv.getFile() if not self.chemDossierSauv: return # Liste pour affichage (ds le tabwidget) listeAff_1=[] listeAff_2=[] listeAff_3=[] # Barre de progression dans une fenêtre séparée . Attention la fenêtre # de la barre se ferme dès que la progression est terminée . En cas de # process très court, la fenêtre peut n'apparaître que très brièvement # (voire pas du tout si le temps est très très court) . self.progress=QProgressDialog(_(u"Progression ..."), _(u"Arrêter le processus"), 0, 100) self.progress.setWindowTitle(_(u'EnKoDeur-Mixeur. Fenêtre de progression')) # Attribution des nouvelles dimensions self.progress.setMinimumWidth(500) self.progress.setMinimumHeight(100) # On implémente les chemins des fichiers dans une variable # pour préparer l'affichage des infos texte1=_(u" Image(s) avec canal alpha chargée(s)") texte2=_(u" Image(s) sans canal alpha chargée(s)") texte3=_(u" Résultat image(s) composite") a='#'*36 self.infosImgProv_1=a+'\n#'+texte1+'\n'+a self.infosImgProv_2=a+'\n#'+texte2+'\n'+a self.infosImgProv_3=a+'\n#'+texte3+'\n'+a # ------------------------------------------------------------------- # Si le nbre d'images chargées avec canal alpha est égal à 1 ... # en fait si l'utilisateur ne charge qu'une image avec et sans canal # alpha ... # ------------------------------------------------------------------- if nbreElem_1 == 1: try : # Ouverture des images (im01 --> avec canal alpha et # im02 --> sans canal alpha) im01=Image.open(self.listeChemAVcanAlph[0]) im02=Image.open(self.listeChemSANScanAlph[0]) # Récup de la dimension des l'image chargée avec canal alpha w_1, h_1=im01.size # Récup de la dimension des l'image chargée sans canal alpha w_2, h_2=im02.size # Redimensionnement de l'image sans canal alpha à la taille de l'image avec canal alpha if (int(w_1), int(h_1)) != (int(w_2), int(h_2)): im02=im02.resize(Image.open(self.listeChemAVcanAlph[0]).size, Image.ANTIALIAS) # Application du compositing imgCompoUndeChaque=Image.composite(im01, im02, im01) # Sauvegarde des images resultant du Compositing self.cheminCourantSauv = self.chemDossierSauv+'_'+string.zfill(self.spin1.value(), self.spin2.value())+'.png' imgCompoUndeChaque.save(self.cheminCourantSauv, "PNG") # Ajout des images par la variable self.cheminCourantSauv dans la liste self.listeChemin # Cette liste sert à récupérer les images pour l'affichage des images ds l'inteface self.listeImgDestin.append(self.cheminCourantSauv) # Affichage des images après traitement # # Changement d'onglet et fonctions associées self.conversionImg = 1 self.metaFctTab(self.indexTabImgDestin) # log listeAff_1.append(self.listeChemAVcanAlph[0]) listeAff_2.append(self.listeChemSANScanAlph[0]) listeAff_3.append(self.cheminCourantSauv) except: messageErreur=QMessageBox(self) messageErreur.setText(_(u"<p>Vous n'avez pas chargé d'image(s) (bouton Ajouter) dans l'onglet <b>Image(s) avec canal aplpha</b>. Recommencez et chargez des images aussi bien dans <b>Image(s) avec canal aplpha</b>, que dans <b>Image(s) sans canal aplpha</b>.</p>")) messageErreur.setWindowTitle(_(u"Erreur")) messageErreur.setIcon(QMessageBox.Critical) messageErreur.exec_() # ------------------------------------------------------------------- # Si le nbre d'images chargées sans canal alpha est supérieur à 1 ... # ce qui correspond à un travail avec un fond animé # ------------------------------------------------------------------- elif nbreElem_2 > 1: self.progress.show() nbre=min(nbreElem_1, nbreElem_2) try : for parcoursComposit_un in range(nbre): # Ouverture des images (imm1 --> avec canal alpha et # imm2 --> sans canal alpha) imm1=Image.open(self.listeChemAVcanAlph[parcoursComposit_un]) imm2=Image.open(self.listeChemSANScanAlph[parcoursComposit_un]) # ------------ Images exactement de même taille --------------------- # Récup des dimensions des images chargées avec canal alpha w_1, h_1=imm1.size # Récup des dimensions des images chargées sans canal alpha w_2, h_2=imm2.size # Si l'utilisateur à chargé des images ayant strictement la même taille if w_1==w_2 and h_1==h_2: # Redimensionnement des images sans canal alpha à la taille # des images avec canal alpha imm2=imm2.resize(Image.open(self.listeChemAVcanAlph[0]).size, Image.ANTIALIAS) # ------------------------------------------------------------------- # ------------ Images de tailles différentes ------------------------ # Si l'utilisateur à chargé des images avec canal alpha ayant la même # taille entre elles mais n'ayant pas la même taille que les images # sans canal alpha (qui ont la même taille entre elles) if w_1!=w_2 or h_1!=h_2: # Redimensionnement des images sans canal alpha à la taille # des images avec canal alpha imm2=imm2.resize(Image.open(self.listeChemAVcanAlph[0]).size, Image.ANTIALIAS) # Si l'utilisateur charge des images avec avec canal alpha de tailles # différentes entre elles et des images sans canal alpha aussi différentes # entre elles, les images sans canal alpha sont redimensionnées à la dimension # des images avec canal alpha if len(repRedimTemp_1)>0 and len(repRedimTemp_2)>0: if (int(w_1), int(h_1)) != (int(w_2), int(h_2)): imm2=imm2.resize(Image.open(repRedimTemp_1[0]).size, Image.ANTIALIAS) # Si l'utilisateur charge des images avec canal alpha toutes de même taille mais # aussi charge des images sans canal alpha de tailles complètement différentes, # les images sans canal alpha sont redimensionnées à la dimension des images # avec canal alpha if len(repRedimTemp_2)>0 and len(repRedimTemp_1)==0: if (int(w_1), int(h_1)) != (int(w_2), int(h_2)): imm2=imm2.resize(Image.open(self.listeChemAVcanAlph[0]).size, Image.ANTIALIAS) # Si l'utilisateur charge des images sans canal alpha toutes de même taille mais # aussi charge des images avec canal alpha de tailles complètement différentes, # les images avec canal alpha sont redimensionnées à la dimension de la 1ère # image avec canal alpha if len(repRedimTemp_1)>0 and len(repRedimTemp_2)==0: if (int(w_1), int(h_1)) != (int(w_2), int(h_2)): imm1=imm1.resize(Image.open(self.listeChemAVcanAlph[0]).size, Image.ANTIALIAS) # Application du compositing imgCompo_1=Image.composite(imm1, imm2, imm1) # Sauvegarde des images resultant du Compositing self.cheminCourantSauv = self.chemDossierSauv+'_'+string.zfill(parcoursComposit_un+self.spin1.value(), self.spin2.value())+'.png' imgCompo_1.save(self.cheminCourantSauv, "PNG") # Ajout des images par la variable self.cheminCourantSauv dans la liste self.listeChemin # Cette liste sert à récupérer les images pour l'affichage des images ds l'inteface self.listeImgDestin.append(self.cheminCourantSauv) # Affichage des images après traitement # # Changement d'onglet et fonctions associées self.conversionImg = 1 self.metaFctTab(self.indexTabImgDestin) # log listeAff_1.append(self.listeChemAVcanAlph[parcoursComposit_un]) listeAff_2.append(self.listeChemSANScanAlph[parcoursComposit_un]) listeAff_3.append(self.cheminCourantSauv) # -------------------------------------------- # Affichage de la progression (avec # QProgressDialog) ds une fenêtre séparée val_pourc_1=((parcoursComposit_un+1)*100)/nbre # Bouton Cancel pour arrêter la progression donc le process if (self.progress.wasCanceled()): break self.progress.setValue(val_pourc_1) QApplication.processEvents() # -------------------------------------------- except: messageErreur=QMessageBox(self) messageErreur.setText(_(u"<p><b>Première situation d'erreur:</b> Vous n'avez pas chargé d'image(s) (bouton Ajouter) dans l'onglet <b>Image(s) avec canal aplpha</b>. Recommencez et chargez des images aussi bien dans <b>Image(s) avec canal alpha</b>, que dans <b>Image(s) sans canal aplpha</b>.</p><p><b>Seconde situation d'erreur:</b> la visualisation et/ou le traitement image(s) ne peut pas avoir lieu car vous avez répondu non au moins une fois au moment du redimensionnement des images. Recommencez et répondez oui aux deux boîtes de dialogue.</p>")) messageErreur.setWindowTitle(_(u"Erreur")) messageErreur.setIcon(QMessageBox.Critical) messageErreur.exec_() # ------------------------------------------------------------------- # Si le nbre d'images chargées sans canal alpha est égal à 1 ... # ce qui correspond à un travail avec un fond fixe (caméra fixe) # ------------------------------------------------------------------- elif nbreElem_2 == 1: self.progress.show() listeTempCompo=[] try : for parcoursDupli in range(nbreElem_1): dupliString=self.listeChemSANScanAlph[0] # Ouverture de l'image reS=Image.open(dupliString) # Sauvegarde de l'image après multiplication reS.save(str(self.repTampon+'d_'+string.zfill(parcoursDupli+self.spin1.value(), self.spin2.value())+'.png'), 'PNG') # Remplissage de la liste tampon listeTempCompo.append(self.repTampon+'d_'+string.zfill(parcoursDupli+self.spin1.value(), self.spin2.value())+'.png') # Copie de l'ensemble des images (avec canal alpha) # chargees par l'utilisateur, dans le sous-repertoire # temporaire --> /EkdConfig.getTempDir()/ekd/tampon/temp_duplication/ . Il est # indispensable que ces images se retrouvent dans le meme # repertoire que les images multipliées (sans canal alpha) for parCop in self.listeChemSANScanAlph: cop=shutil.copy(parCop, self.repTampon) # Ouverture des images (imm3 --> avec canal alpha et # imm4 --> sans canal alpha) imm3=Image.open(self.listeChemAVcanAlph[parcoursDupli]) imm4=Image.open(listeTempCompo[parcoursDupli]) # ------------ Images de même taille -------------------------------- # Récup des dimensions des images chargées avec canal alpha w_1, h_1=imm3.size # Récup des dimensions des images chargées sans canal alpha w_2, h_2=imm4.size # Si l'utilisateur à chargé des images ayant strictement la même taille if w_1==w_2 and h_1==h_2: # Redimensionnement des images sans canal alpha à la taille # des images avec canal alpha imm4=imm4.resize(Image.open(self.listeChemAVcanAlph[0]).size, Image.ANTIALIAS) # ------------------------------------------------------------------- # ------------ Images de tailles différentes ------------------------ # Si le rep /EkdConfig.getTempDir()/ekd/tampon/temp_duplication/redim_1 contient des fichiers ... if len(repRedimTemp_1)>0: # Ouverture de la dernière image dans: # /EkdConfig.getTempDir()/ekd/tampon/temp_duplication/redim_1 imm3=Image.open(repRedimTemp_1[parcoursDupli]) # Dimension (w_1 --> largeur, h_1 --> hauteur) w_1, h_1=imm3.size # Si le rep /EkdConfig.getTempDir()/ekd/tampon/temp_duplication/redim_2 contient des fichiers ... if len(repRedimTemp_2)>0: # Ouverture de la première image dans: # /EkdConfig.getTempDir()/ekd/tampon/temp_duplication/redim_2 imm4=Image.open(repRedimTemp_2[parcoursDupli]) # Dimension (w_2 --> largeur, h_2 --> hauteur) w_2, h_2=imm4.size # Si l'utilisateur charge des images avec canal alpha de taille complètement différentes # (les images sont redimensionnées ds le rep tempo redim_1)de l'image sans canal alpha, # l'image sans canal alpha est redimensionnée à la taille des images avec canal alpha du # répertoire temporaire. Autrement l'image sans canal alpha est redimensionnée à la taille # de la 1ère image avec canal alpha chargée if len(repRedimTemp_1)>0: if (int(w_1), int(h_1)) != (int(w_2), int(h_2)): imm4=imm4.resize(Image.open(repRedimTemp_1[0]).size, Image.ANTIALIAS) else: imm4=imm4.resize(Image.open(self.listeChemAVcanAlph[0]).size, Image.ANTIALIAS) # Application du compositing . imgCompo_2=Image.composite(imm3, imm4, imm3) # Sauvegarde des images resultant du Compositing self.cheminCourantSauv = self.chemDossierSauv+'_'+string.zfill(parcoursDupli+self.spin1.value(), self.spin2.value())+'.png' # ---------------------------------------------------------------------- imgCompo_2.save(self.cheminCourantSauv, "PNG") # Ajout des images par la variable self.cheminCourantSauv dans la liste self.listeChemin # Cette liste sert à récupérer les images pour l'affichage des images ds l'inteface self.listeImgDestin.append(self.cheminCourantSauv) # Affichage des images après traitement # # Changement d'onglet et fonctions associées self.conversionImg = 1 self.metaFctTab(self.indexTabImgDestin) # log listeAff_1.append(self.listeChemAVcanAlph[parcoursDupli]) listeAff_2.append(listeTempCompo[parcoursDupli]) listeAff_3.append(self.cheminCourantSauv) # -------------------------------------------- # Affichage de la progression (avec # QProgressDialog) ds une fenêtre séparée . val_pourc_2=((parcoursDupli+1)*100)/nbreElem_1 # Bouton Cancel pour arrêter la progression donc le process if (self.progress.wasCanceled()): break self.progress.setValue(val_pourc_2) QApplication.processEvents() # -------------------------------------------- except: messageErreur=QMessageBox(self) messageErreur.setText(_(u"<p><b>Première situation d'erreur:</b> Vous n'avez pas chargé d'image(s) (bouton Ajouter) dans l'onglet <b>Image(s) avec canal aplpha</b>. Recommencez et chargez des images aussi bien dans <b>Image(s) avec canal alpha</b>, que dans <b>Image(s) sans canal aplpha</b>.</p><p><b>Seconde situation d'erreur:</b> la visualisation et/ou le traitement image(s) ne peut pas avoir lieu car vous avez répondu non au moins une fois au moment du redimensionnement des images. Recommencez et répondez oui aux deux boîtes de dialogue.</p>")) messageErreur.setWindowTitle(_(u"Erreur")) messageErreur.setIcon(QMessageBox.Critical) messageErreur.exec_() # Images chargées avec canal alpha for parcStatRendu_1 in listeAff_1: self.infosImgProv_1=self.infosImgProv_1+'\n'+parcStatRendu_1 # Pages sauvegardées for parcStatRendu_2 in listeAff_2: self.infosImgProv_2=self.infosImgProv_2+'\n'+parcStatRendu_2 # Compositing for parcStatRendu_3 in listeAff_3: self.infosImgProv_3=self.infosImgProv_3+'\n'+parcStatRendu_3 # affichage des infos dans l'onglet self.zoneAffichInfosImg.setText(self.infosImgProv_1+'\n\n'+self.infosImgProv_2+'\n\n'+self.infosImgProv_3+'\n\n') self.fram.setEnabled(True) def afficherAide(self): """Boîte de dialogue de l'aide""" # Utilisation de EkdAide messageAide=EkdAide(parent=self) messageAide.setText(tr(u"<p><b>Vous allez ici superposer des images avec un canal alpha (transparence) sur d'autres images sans canal alpha (ces dernières sont en quelque sorte l'arrière-plan). Vous avez aussi ici la possibilité de travailler avec un arrière-plan composé d'une seule image, en cas de travail en plan fixe (le programme duplique lui-même les images).</b></p><p><b>Voilà la définition que donne Wikipédia du terme compositing (c'est le terme exact): 'La composition (en anglais compositing) est un ensemble de méthodes numériques consistant à mélanger plusieurs sources d’images pour en faire un plan unique qui sera intégré dans le montage. Pour un film d'animation, il s'agit de l'étape finale de fabrication qui consiste à assembler toutes les couches des décors, des personnages et à réaliser les effets de caméra, à animer certains déplacements, et effets spéciaux. En cinéma de prise de vue réel, il consiste surtout à réaliser des effets spéciaux et à truquer des vidéos. C'est l'un des derniers maillons de la chaîne de l'image dans la réalisation d'un film.<br>Les sources peuvent être des images numérisées de cinéma, de dessin, de vidéo, des images numériques (dessin, 3D, effets spéciaux).'<br>Source: http://fr.wikipedia.org/wiki/Compositing</b></p><p>Dans l'onglet <b>'Image(s) avec canal alpha'</b> cliquez sur le bouton <b>Ajouter</b>, une boîte de dialogue apparaît, sur la partie gauche sélectionnez le répertoire (au besoin dépliez les sous-répertoires), allez chercher vos image(s). Passez maintenant dans l'onglet <b>'Image(s) sans canal alpha'</b> cliquez sur le bouton <b>Ajouter</b>, une boîte de dialogue apparaît, sur la partie gauche sélectionnez le répertoire (au besoin dépliez les sous-répertoires), allez chercher vos image(s). Si vous voulez sélectionner plusieurs images d'un coup, maintenez la touche <b>CTRL</b> (ou <b>SHIFT</b>) du clavier enfoncée (tout en sélectionnant vos images).</p><p>Dans <b>'Réglages'</b> faites les réglages du <b>'Traitement à partir de l'image (numéro)'</b> et du <b>'Nombre de chiffres après le nom de l'image' <font color='red'>(la plupart du temps les valeurs par défaut suffisent)</font></b>. Cliquez sur le bouton <b>'Voir le résultat'</b> vous voyez à ce moment là, le résultat du compositing entre la dernière image du lot de votre premier groupe d'image (images avec transparence) et la première image du lot de votre second groupe d'image (images sans transparence), s'afficher dans une nouvelle fenêtre.</p>Pour finir cliquez sur le bouton <b>'Appliquer et sauver'</b>, entrez le titre de votre futur compositing (après <b>'Nom de fichier'</b>) dans cette dernière boîte (vous aurez évidemment pris soin de sélectionner le répertoire de destination de votre compositing). Cliquez sur le bouton <b>'Enregistrer'</b>.<p>Si vous faites un clic droit de la souris (sur l'image) dans l'onglet <b>Image(s) après traitement</b>, vous accédez à des paramètres vous permettant différents affichages de la dite image. De même dans cet onglet vous pouvez lancer la visualisation des images par le bouton <b>Lancer le diaporama</b> (le bouton violet avec une flèche blanche vers la droite).</p><p>L'onglet <b>'Infos'</b> vous permet de voir le filtre utilisé, les image(s) chargée(s) et les image(s) convertie(s).</p>")) messageAide.show() def save(self) : self.afficheurImgSourceAvecCanalAlpha.saveFileLocation(self.idSection, u'sourcesa') self.afficheurImgSourceSansCanalAlpha.saveFileLocation(self.idSection, u'sourcessa') EkdConfig.set(self.idSection, u'spin1', unicode(self.spin1.value())) EkdConfig.set(self.idSection, u'spin2', unicode(self.spin2.value())) def load(self) : self.afficheurImgSourceAvecCanalAlpha.loadFileLocation(self.idSection, u'sourcesa') self.afficheurImgSourceSansCanalAlpha.loadFileLocation(self.idSection, u'sourcessa') self.spin1.setValue(int(EkdConfig.get(self.idSection, u'spin1'))) self.spin2.setValue(int(EkdConfig.get(self.idSection, u'spin2')))
class Image_Divers_TxtSurImg(QWidget): # ----------------------------------- # Cadre accueillant les widgets de : # Image >> Divers >> Texte sur images # ----------------------------------- ZOOM_MOINS,ZOOM_PLUS,ZOOM_REEL,ZOOM_FIT = range(4) def __init__(self, statusBar, geometry): QWidget.__init__(self) # Fonctions communes à plusieurs cadres du module Image self.base = Base() # Gestion de la configuration via EkdConfig # Paramètres de configuration self.config = EkdConfig self.nProc = 0 vbox = QVBoxLayout() # Layout principal # Modification de structure pour mettre ce module conforme au nouveau design. # 1.Ajout du tab principal self.tabwidget=QTabWidget() # 2. Création du tab de sélection de fichier source - basé sur le widget de sélection standard self.afficheurImgSource=SelectWidget(geometrie = geometry) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "image" # Défini le type de fichier source. self.typeSortie = "image" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurImgSource # Fait le lien avec le sélecteur de fichier source. self.connect(self.afficheurImgSource, SIGNAL("pictureChanged(int)"), self.modifImgSource) self.connect(self.afficheurImgSource, SIGNAL("pictureSelected()"), self.modifImgSource) self.tabwidget.addTab(self.afficheurImgSource, _(u'Image(s) source')) # 3. Création du tab réglages self.listFileNames = [] self.z = 1 # "Hauteur" initiale des graphicsItems ajoutés self.baseImage = None self.copiedItem = QByteArray() self.pasteOffset = 5 self.prevPoint = QPoint() self.addOffset = 5 self.borders = [] self.widReglage = QWidget() self.view = GraphicsView() self.scene = QGraphicsScene(self) self.view.setScene(self.scene) # Image de fond self.background = QGraphicsPixmapItem() buttonLayout = QVBoxLayout() # Liste qui servira à activer/désactiver les boutons self.buttonsList = [] # Liste des boutons à éviter sous PyQt4.1.0 (ex.ubuntu feisty) à cause d'un bogue # sur les fonctions associées self.buttonListeNoireFeisty = [] self.zoomPlus = partial(self.zoom, Image_Divers_TxtSurImg.ZOOM_PLUS) self.zoomMoins = partial(self.zoom, Image_Divers_TxtSurImg.ZOOM_MOINS) self.zoomReel = partial(self.zoom, Image_Divers_TxtSurImg.ZOOM_REEL) self.zoomFit = partial(self.zoom, Image_Divers_TxtSurImg.ZOOM_FIT) layoutEdition = QHBoxLayout() for text, slot, icon in ( (_(u"Ajouter &Texte"), self.addText, "Icones/text_add.png"), (_(u"Ajouter &Boite"), self.addBox, "Icones/ajouter_shape.png"), (_(u"Ajouter Ima&ge"), self.addPixmap, "Icones/image.png"), (_(u"C&opier"), self.copy, "Icones/copy.png"), (_(u"Cou&per"), self.cut, "Icones/cut.png"), (_(u"Co&ller"), self.paste, "Icones/paste.png"), (_(u"&Effacer..."), self.delete, "Icones/bin.png"), (_(u"&Monter"), self.upItem, "Icones/up.png"), (_(u"Descen&dre"), self.downItem, "Icones/down.png"), (_(u"Pivoter &Droite"), self.rotateR, "Icones/rotationd.png"), (_(u"Pivoter &Gauche"), self.rotateL, "Icones/rotationg.png"), (_(u"Zoom &fit"), self.zoomFit, "Icones/fenetre.png"), (_(u"Zoom &avant"), self.zoomPlus, "Icones/zoomplus.png"), (_(u"Zoom a&rrière"), self.zoomMoins, "Icones/zoommoins.png"), (_(u"Zoom &100%"), self.zoomReel, "Icones/taillereelle.png"), ): if text in [_(u"C&opier"),_(u"Cou&per"),_(u"Co&ller"),_(u"&Effacer...")]: button = QToolButton() button.setIcon(QIcon(icon)) button.setIconSize(QSize(16, 16)) button.setToolTip(text) layoutEdition.addWidget(button) self.buttonListeNoireFeisty.append(button) else : if icon != None : button = QPushButton(QIcon(icon), text) else : button = QPushButton(text) buttonLayout.addWidget(button) button.setEnabled(False) self.buttonsList.append(button) self.connect(button, SIGNAL("clicked()"), slot) if text == _(u"&Effacer...") : # provisoire... buttonLayout.addLayout(layoutEdition) buttonLayout.addStretch(0) if text == _(u"Pivoter &Gauche"): # prochainement... buttonLayout.addStretch(0) buttonLayout.addStretch() # ComboBox de choix du format de sortie self.choixSortie = QComboBox() self.listFormatSortie = [] self.indexFormatSortie = 0 for text, format, extension, compression in ( (_(u"PNG"),"PNG",u".png", 1), (_(u"JPEG"),"JPEG",u".jpg", 2), (_(u"BMP"),"BMP",u".bmp", 0), (_(u"TIFF"),"TIFF",u".tiff", 0), (_(u"PPM"),"PPM",u".ppm", 0)) : self.choixSortie.addItem(text) self.listFormatSortie.append([format,extension,compression]) self.connect(self.choixSortie, SIGNAL("currentIndexChanged(int)"), self.setFormatSortie) buttonLayout.addWidget(self.choixSortie) self.labelQualite = QLabel(_(u"Qualité de l'image")) self.cbQualite = QComboBox() self.cbQualite.addItems(["0","10","20","30","40","50","60","70","80","85","90","95","100"]) self.cbQualite.setCurrentIndex(8) hboxQ = QHBoxLayout() hboxQ.addWidget(self.labelQualite) hboxQ.addWidget(self.cbQualite) buttonLayout.addLayout(hboxQ) self.labelQualite.hide() self.cbQualite.hide() hbox = QHBoxLayout() hbox.addWidget(self.view, 1) hbox.addLayout(buttonLayout) self.widReglage.setLayout(hbox) self.tabwidget.addTab(self.widReglage, _(u"Réglage")) # 4. Création du tab de visualisation du résultat self.afficheurImgDestination=Lecture_VisionImage(statusBar) self.tabwidget.addTab(self.afficheurImgDestination, _(u'Visualisation des images créées')) # 5. Création du tab Info self.Logs = QTextEdit() # Zone d'affichage des infos (Zone acceptant le formatage HTML et avec assenceur automatique si nécessaire) self.tabwidget.addTab(self.Logs, _(u'Infos')) #----------------- # widgets du bas #----------------- # Boutons boutAide=QPushButton(_(u" Aide")) boutAide.setIcon(QIcon("Icones/icone_aide_128.png")) self.connect(boutAide, SIGNAL("clicked()"), self.afficherAide) self.boutAppliquer=QPushButton(_(u"Appliquer")) self.boutAppliquer.setIcon(QIcon("Icones/icone_appliquer_128.png")) # Bouton inactif au départ self.boutAppliquer.setEnabled(False) self.connect(self.boutAppliquer, SIGNAL("clicked()"), self.appliquer) # Ligne de séparation juste au dessus des boutons ligne = QFrame() ligne.setFrameShape(QFrame.HLine) ligne.setFrameShadow(QFrame.Sunken) hbox=QHBoxLayout() hbox.addWidget(boutAide) hbox.addStretch() hbox.addWidget(self.boutAppliquer) vbox.addWidget(self.tabwidget) vbox.addWidget(ligne) vbox.addLayout(hbox) self.setLayout(vbox) def setFormatSortie(self, index) : self.indexFormatSortie = index if self.listFormatSortie[index][2] == 2 : self.labelQualite.show() self.cbQualite.show() else : self.labelQualite.hide() self.cbQualite.hide() def modifImgSource(self, i=True): """On active ou désactive les boutons d'action et on recharge le pseudo-aperçu de planche-contact en fonction du nombre d'images présentes dans le widget de sélection""" ###self.displayImg() self.boutAppliquer.setEnabled(i) selectedImg = self.afficheurImgSource.getFile() if self.setBackgroundImg(selectedImg) : if PYQT_VERSION_STR != "4.1.0": for button in self.buttonsList: button.setEnabled(True) else: # Pour empêcher le bogue de PyQt4.1.0 Feisty for button in self.buttonsList: if button not in self.buttonListeNoireFeisty: button.setEnabled(True) self.zoom(Image_Divers_TxtSurImg.ZOOM_FIT) def setBackgroundImg (self, selectedImg) : if selectedImg and selectedImg != self.baseImage : self.baseImage = selectedImg # Création de l'objet QImage de l'image de base pixmap = QPixmap(self.baseImage) # Suppression de la précédente image si elle existait if not self.background.pixmap().isNull(): self.scene.removeItem(self.background) # Ajout de la nouvelle image self.scene.setSceneRect(0, 0, pixmap.width(), pixmap.height()) self.background = self.scene.addPixmap(pixmap) self.background.setZValue(0) return True else : return False def position(self, pixmap=None): #print "QCursor.pos()", QCursor.pos().x(), QCursor.pos().y() EkdPrint(u"QCursor.pos() %s %s" % (QCursor.pos().x(), QCursor.pos().y())) point = self.mapFromGlobal(QCursor.pos()) #print "self.mapFromGlobal(QCursor.pos()", point.x(), point.y() EkdPrint(u"self.mapFromGlobal(QCursor.pos() %s %s" % (point.x(), point.y())) if not self.view.geometry().contains(point) or pixmap: #print "if not self.view.geometry().contains(point) or pixmap" EkdPrint(u"if not self.view.geometry().contains(point) or pixmap") coord = random.randint(36, 144) point = QPoint(coord, coord) else: #print "else de if not self.view..." EkdPrint(u"else de if not self.view...") if point == self.prevPoint: #print "if point == self.prevPoint" EkdPrint(u"if point == self.prevPoint") point += QPoint(self.addOffset, self.addOffset) self.addOffset += 5 else: #print "else de if point..." EkdPrint(u"else de if point...") self.addOffset = 5 self.prevPoint = point #print "point", point.x(), point.y(), '\n' EkdPrint("upoint %s %s\n" % (point.x(), point.y())) # La position du point haut-gauche du rectangle encadrant le texte #print "self.view.mapToScene(point)", self.view.mapToScene(point).x(), self.view.mapToScene(point).y() EkdPrint(u"self.view.mapToScene(point) %s %s" % (self.view.mapToScene(point).x(), self.view.mapToScene(point).y())) return self.view.mapToScene(point) def addText(self): self.z += 1 dialog = TextItemDlg(position=self.position(), scene=self.scene, parent=self, z=self.z) dialog.exec_() def addBox(self): self.z += 1 BoxItem(self.position(), self.scene, z=self.z) def addPixmap(self): formats = ["*.%s" % unicode(format).lower() \ for format in QImageReader.supportedImageFormats()] path = self.base.getRepSource(self.config) txt = _(u"Fichiers Image") fname = unicode(QFileDialog.getOpenFileName(self, "Ouvrir des images", path, "%s (%s);;%s" %(txt, " ".join(formats), FORMAT))) if not fname: return self.base.setRepSource(self.config, fname) self.createPixmapItem(QPixmap(fname), self.position(pixmap=True)) def createPixmapItem(self, pixmap, position, rotation=0): self.z += 1 item = GraphicsPixmapItem(pixmap, position, rotation, z=self.z) item.setFlags(QGraphicsItem.ItemIsSelectable| QGraphicsItem.ItemIsMovable) self.scene.clearSelection() self.scene.addItem(item) item.setZValue(self.z+1) self.z += 1 item.setSelected(True) def selectedItem(self): items = self.scene.selectedItems() if len(items) == 1: return items[0] return None def copy(self): item = self.selectedItem() if item is None: return self.copiedItem.clear() self.pasteOffset = 5 stream = QDataStream(self.copiedItem, QIODevice.WriteOnly) self.writeItemToStream(stream, item) def cut(self): item = self.selectedItem() if item is None: return self.copy() self.scene.removeItem(item) del item def paste(self): if self.copiedItem.isEmpty(): return stream = QDataStream(self.copiedItem, QIODevice.ReadOnly) self.readItemFromStream(stream, self.pasteOffset) self.pasteOffset += 5 def upItem(self) : '''Fonction pour relever le/les item(s) sélectionné(s))''' for item in self.scene.selectedItems(): z = item.zValue() item.setZValue(z+1) def downItem(self) : '''Fonction pour descendre le/les item(s) sélectionné(s))''' for item in self.scene.selectedItems(): z = item.zValue() if z>1 : # Pour éviter de placer un élément en dessous de l'image source qui est au niveau 0. item.setZValue(z-1) def rotateR(self): for item in self.scene.selectedItems(): item.setRotateR(22.5) def rotateL(self): for item in self.scene.selectedItems(): item.setRotateL(22.5) def delete(self): items = self.scene.selectedItems() #print "items sélectionnés", items ### EkdPrint(u"items sélectionnés %s" % items) txt1,txt2=_(u"Effacer"),_(u"élément") if len(items) != 1: s = "s" else: s = "" if len(items) and QMessageBox.question(self, _(u"Texte sur images - Effacer"), "%s %d %s%s?" % (txt1,len(items),txt2,s), QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes: while items: item = items.pop() #print "item?", item EkdPrint(u"item? %s" % item) self.scene.removeItem(item) del item def readItemFromStream(self, stream, offset=0): type_ = QString() position = QPointF() stream >> type_ >> position rotation = stream.readInt16() if offset: position += QPointF(offset, offset) if type_ == "Text": text = QString() font = QFont() color = QColor() stream >> text >> font >> color self.z += 1 TextItem(text, position, self.scene, font, color, rotation, self.z) elif type_ == "Pixmap": pixmap = QPixmap() stream >> pixmap self.createPixmapItem(pixmap, position, rotation) elif type_ == "Box": rect = QRectF() stream >> rect style = Qt.PenStyle(stream.readInt16()) borderWidth = stream.readInt16() boxColor = QColor() stream >> boxColor self.z += 1 BoxItem(position, self.scene, style, borderWidth, boxColor, rect, rotation, self.z) def writeItemToStream(self, stream, item): if isinstance(item, QGraphicsTextItem): stream << QString("Text") << item.pos() stream.writeInt16(item.getRotate()) stream << item.toPlainText() << item.font() << item.defaultTextColor() elif isinstance(item, QGraphicsPixmapItem): stream << QString("Pixmap") << item.pos() stream.writeInt16(item.getRotate()) stream << item.pixmap() elif isinstance(item, BoxItem): stream << QString("Box") << item.pos() stream.writeInt16(item.getRotate()) stream << item.rect stream.writeInt16(item.style) stream.writeInt16(item.borderWidth) stream << item.boxColor def appliquer(self): "Conversion des images: ajout de textes, images et boites" # Utilisation de la nouvelle boîte de dialogue de sauvegarde suffix="" fname = EkdSaveDialog(self, mode="image", suffix=suffix, title=_(u"Sauver"), multiple=True) fname = fname.getFile() if not fname: return # Gestion de l'extension if fname.endswith(self.listFormatSortie[self.indexFormatSortie][1]) : fname = fname[:-len(self.listFormatSortie[self.indexFormatSortie][1])] # Progression progress=QProgressDialog(_(u"Conversion en cours..."), _(u"Arrêter"), 0, 100) progress.setWindowTitle(_(u'EnKoDeur-Mixeur. Fenêtre de progression')) progress.show() progress.setValue(0) # Module traitement par lot lstImg = self.afficheurImgSource.getFiles() nbrImg = len(lstImg) lstFname = [] k = 1 for bimg in lstImg : self.setBackgroundImg(bimg) # Enregistrement de l'image composée imgDim = self.scene.sceneRect() imgFinal = QImage(imgDim.width(), imgDim.height(), QImage.Format_ARGB32) self.scene.clearSelection() self.scene.render(QPainter(imgFinal)) if self.listFormatSortie[self.indexFormatSortie][2] == 2 : # Qualité = valeur de la combobox qu = int(self.cbQualite.currentText()) elif self.listFormatSortie[self.indexFormatSortie][2] == 0 : # Qualité 100 pour les images sans compression qu = 100 else : # Compression maximale pour les images PNG car format non destructif. qu = 0 if imgFinal.save(fname+string.zfill(str(k), 5)+self.listFormatSortie[self.indexFormatSortie][1], self.listFormatSortie[self.indexFormatSortie][0], qu) : lstFname.append(fname+string.zfill(str(k), 5)+self.listFormatSortie[self.indexFormatSortie][1]) else : #print "Erreur lors de la sauvegarde de l'image" EkdPrint(u"Erreur lors de la sauvegarde de l'image") progress.setValue(int(100*k/nbrImg)) k += 1 # Affichage du résultat self.afficheurImgDestination.cheminImage = u"" self.afficheurImgDestination.updateImages(lstFname) # Mise à jour du log self.updateLog(lstImg, lstFname) def updateLog(self, images, sorties) : """Fonction pour la mise à jour des informations de log données à l'utilisateur en fin de process sur les données entrées, et le résultat du process""" msgIm = _(u"<p>####################################<br># Image(s) chargée(s)<br>####################################</p>") for i in images : msgIm += unicode(i)+u"<br>" msgOut = _(u"<br><p>####################################<br># Fichier(s) de sortie<br>####################################</p>") for s in sorties : msgOut += unicode(s)+u"<br>" self.Logs.setHtml(QString(msgIm+msgOut)) def zoom(self, val): if val==Image_Divers_TxtSurImg.ZOOM_PLUS: self.view.scale(1.2, 1.2) elif val==Image_Divers_TxtSurImg.ZOOM_MOINS: self.view.scale(0.8, 0.8) elif val==Image_Divers_TxtSurImg.ZOOM_REEL: self.view.resetTransform() elif val==Image_Divers_TxtSurImg.ZOOM_FIT : self.view.fitInView(self.background, Qt.KeepAspectRatio) def afficherAide(self): "Boîte de dialogue de l'aide" messageAide = EkdAide(parent=self) messageAide.setText(_(u"<p><b>Ici vous pouvez ajouter du texte, des cadres ou des petites images comme des logos sur des images. Cela peut être utile pour <i>signer</i> ou indiquer la provenance d'un lot d'images (avant par exemple de les diffuser sur internet).</b></p><p><b>De nombreux réglages vous sont proposés (dans l'onglet 'Réglage') afin d'inscrire et disposer comme bon vous semble du texte sur un lot d'images.</b></p>\ \ <p>Dans l'onglet <b>Image(s) source</b> cliquez sur le bouton <b>Ajouter</b>, une boîte de dialogue apparaît, sur la partie gauche sélectionnez le répertoire (au besoin dépliez les sous-répertoires), allez chercher vos image(s). Si vous voulez sélectionner plusieurs images d'un coup, maintenez la touche <b>CTRL</b> (ou <b>SHIFT</b>) du clavier enfoncée (tout en sélectionnant vos images), cliquez sur <b>Ajouter</b>. Toujours dans l'onglet <b>Image(s) source</b> sélectionnez une des miniatures.</p><p>Allez maintenant dans l'onglet <b>Réglages</b>, l'image de la miniature que vous venez de sélectionner est affichée. Vous pouvez zoomer ou dézoomer l'image avec le bouton du milieu de la souris. Dans cet onglet <b>Réglages</b> (et sur la droite), vous voyez toute une série de boutons (qui correspondent aux différentes actions que vous pouvez effectuer sur l'image ou sur le texte que vous allez écrire sur cette image). Nous allons maintenant, simplement ajouter un texte, pour ce faire cliquez sur le bouton <b>Ajouter texte</b>, la boîte de dialogue <b>Texte sur images - Ajouter texte</b> apparaît, avant d'écrire quoi que ce soit (dans le champ réservé à cet effet), faites le choix de la <b>Police</b>, de la <b>Taille</b> et de la <b>Couleur</b>, écrivez maintenant votre texte, une fois fait, cliquez sur le bouton <b>OK</b>. Vérifiez que votre texte ne dépasse pas de l'image (si cela est la cas, double-cliquez sur le texte en question, la boîte de dialogue de saisie va réapparaître) et faites ce qu'il faut ... . Vous pouvez positionner le texte sur l'image en bougeant le cadre où se trouve le texte avec la souris. Vous pouvez si vous le désirez ajouter une boîte (par le bouton <b>Ajouter Boîte</b>). Pour choisir le contour de la boîte, faites un clic droit dessus, un menu vous permettra de choisir le contour que vous désirez. En ce qui concerne la taille de la boîte, il est possible de régler la taille de celle-ci en pressant simultanément les touches <b>Shift</b> et <b>Flèche droite (ou gauche)</b> pour changer la taille en largeur, <b>Shift</b> et <b>Flèche haut (ou bas)</b> pour changer la taille en hauteur (pas besoin d'utiliser la souris pour changer la taille). Vous pouvez ajouter une image (par le bouton <b>Ajouter Image</b>), sélectionnez le chemin de votre image (puis l'image elle-même) dans la boîte de dialogue <b>Ouvrir des images</b>, cliquez sur le bouton <b>Ouvrir</b>. Certaines actions peuvent être effectuées (comme Copier, Couper, Coller, Effacer) par les boutons juste en dessous de <b>Ajouter Image</b> (positionnez votre souris sur ces boutons et il vous sera indiqué à quoi ils correspondent). Vous pouvez déplacer les éléments un à un dans la scène en cliquant dessus et en déplaçant la souris sans relacher le clic gauche (vu précédemment). Vous pouvez aussi déplacer plusieurs éléments en même temps comme expliqué précédemment mais en 'dessinant' au préalable un rectangle qui les englobera (bouton gauche de la souris maintenu en la déplaçant). L'ordre d'affichage des éléments peut-être modifié au moyen des boutons <b>Monter</b> et <b>Descendre</b>.</p><p>Sélectionnez votre format d'image (PNG, JPEG, BMP, TIFF ou PPM), c'est à dire votre extension d'image, dans la liste déroulante (tout en bas) prévue à cet effet. Dans le cas d'une sélection JPEG, n'oubliez pas de régler la <b>Qualité de l'image</b>.</p><p>Une fois tout ceci fait, cliquez sur le bouton <b>Appliquer</b>, sélectionnez le répertoire de sauvegarde, indiquez votre <b>Nom de fichier</b>, cliquez sur le bouton <b>Enregistrer</b>.</p><p>Si vous faites un clic droit de la souris (sur l'image) dans l'onglet <b>Visualisation des images créées</b>, vous accédez à des paramètres vous permettant différents affichages de la dite image. De même dans cet onglet vous pouvez lancer la visualisation des images par le bouton <b>Lancer le diaporama</b> (le bouton violet avec une flèche blanche vers la droite).</p><p>L'onglet <b>Infos</b> vous permet de voir le filtre utilisé, les image(s) chargée(s) et les image(s) convertie(s).</p>")) messageAide.show() def save(self) : self.afficheurImgSource.saveFileLocation(self.idSection) def load(self) : self.afficheurImgSource.loadFileLocation(self.idSection)
def __init__(self, statusBar, geometry): QWidget.__init__(self) # Fonctions communes à plusieurs cadres du module Image self.base = Base() # Gestion de la configuration via EkdConfig # Paramètres de configuration self.config = EkdConfig self.nProc = 0 vbox = QVBoxLayout() # Layout principal # Modification de structure pour mettre ce module conforme au nouveau design. # 1.Ajout du tab principal self.tabwidget=QTabWidget() # 2. Création du tab de sélection de fichier source - basé sur le widget de sélection standard self.afficheurImgSource=SelectWidget(geometrie = geometry) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "image" # Défini le type de fichier source. self.typeSortie = "image" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurImgSource # Fait le lien avec le sélecteur de fichier source. self.connect(self.afficheurImgSource, SIGNAL("pictureChanged(int)"), self.modifImgSource) self.connect(self.afficheurImgSource, SIGNAL("pictureSelected()"), self.modifImgSource) self.tabwidget.addTab(self.afficheurImgSource, _(u'Image(s) source')) # 3. Création du tab réglages self.listFileNames = [] self.z = 1 # "Hauteur" initiale des graphicsItems ajoutés self.baseImage = None self.copiedItem = QByteArray() self.pasteOffset = 5 self.prevPoint = QPoint() self.addOffset = 5 self.borders = [] self.widReglage = QWidget() self.view = GraphicsView() self.scene = QGraphicsScene(self) self.view.setScene(self.scene) # Image de fond self.background = QGraphicsPixmapItem() buttonLayout = QVBoxLayout() # Liste qui servira à activer/désactiver les boutons self.buttonsList = [] # Liste des boutons à éviter sous PyQt4.1.0 (ex.ubuntu feisty) à cause d'un bogue # sur les fonctions associées self.buttonListeNoireFeisty = [] self.zoomPlus = partial(self.zoom, Image_Divers_TxtSurImg.ZOOM_PLUS) self.zoomMoins = partial(self.zoom, Image_Divers_TxtSurImg.ZOOM_MOINS) self.zoomReel = partial(self.zoom, Image_Divers_TxtSurImg.ZOOM_REEL) self.zoomFit = partial(self.zoom, Image_Divers_TxtSurImg.ZOOM_FIT) layoutEdition = QHBoxLayout() for text, slot, icon in ( (_(u"Ajouter &Texte"), self.addText, "Icones/text_add.png"), (_(u"Ajouter &Boite"), self.addBox, "Icones/ajouter_shape.png"), (_(u"Ajouter Ima&ge"), self.addPixmap, "Icones/image.png"), (_(u"C&opier"), self.copy, "Icones/copy.png"), (_(u"Cou&per"), self.cut, "Icones/cut.png"), (_(u"Co&ller"), self.paste, "Icones/paste.png"), (_(u"&Effacer..."), self.delete, "Icones/bin.png"), (_(u"&Monter"), self.upItem, "Icones/up.png"), (_(u"Descen&dre"), self.downItem, "Icones/down.png"), (_(u"Pivoter &Droite"), self.rotateR, "Icones/rotationd.png"), (_(u"Pivoter &Gauche"), self.rotateL, "Icones/rotationg.png"), (_(u"Zoom &fit"), self.zoomFit, "Icones/fenetre.png"), (_(u"Zoom &avant"), self.zoomPlus, "Icones/zoomplus.png"), (_(u"Zoom a&rrière"), self.zoomMoins, "Icones/zoommoins.png"), (_(u"Zoom &100%"), self.zoomReel, "Icones/taillereelle.png"), ): if text in [_(u"C&opier"),_(u"Cou&per"),_(u"Co&ller"),_(u"&Effacer...")]: button = QToolButton() button.setIcon(QIcon(icon)) button.setIconSize(QSize(16, 16)) button.setToolTip(text) layoutEdition.addWidget(button) self.buttonListeNoireFeisty.append(button) else : if icon != None : button = QPushButton(QIcon(icon), text) else : button = QPushButton(text) buttonLayout.addWidget(button) button.setEnabled(False) self.buttonsList.append(button) self.connect(button, SIGNAL("clicked()"), slot) if text == _(u"&Effacer...") : # provisoire... buttonLayout.addLayout(layoutEdition) buttonLayout.addStretch(0) if text == _(u"Pivoter &Gauche"): # prochainement... buttonLayout.addStretch(0) buttonLayout.addStretch() # ComboBox de choix du format de sortie self.choixSortie = QComboBox() self.listFormatSortie = [] self.indexFormatSortie = 0 for text, format, extension, compression in ( (_(u"PNG"),"PNG",u".png", 1), (_(u"JPEG"),"JPEG",u".jpg", 2), (_(u"BMP"),"BMP",u".bmp", 0), (_(u"TIFF"),"TIFF",u".tiff", 0), (_(u"PPM"),"PPM",u".ppm", 0)) : self.choixSortie.addItem(text) self.listFormatSortie.append([format,extension,compression]) self.connect(self.choixSortie, SIGNAL("currentIndexChanged(int)"), self.setFormatSortie) buttonLayout.addWidget(self.choixSortie) self.labelQualite = QLabel(_(u"Qualité de l'image")) self.cbQualite = QComboBox() self.cbQualite.addItems(["0","10","20","30","40","50","60","70","80","85","90","95","100"]) self.cbQualite.setCurrentIndex(8) hboxQ = QHBoxLayout() hboxQ.addWidget(self.labelQualite) hboxQ.addWidget(self.cbQualite) buttonLayout.addLayout(hboxQ) self.labelQualite.hide() self.cbQualite.hide() hbox = QHBoxLayout() hbox.addWidget(self.view, 1) hbox.addLayout(buttonLayout) self.widReglage.setLayout(hbox) self.tabwidget.addTab(self.widReglage, _(u"Réglage")) # 4. Création du tab de visualisation du résultat self.afficheurImgDestination=Lecture_VisionImage(statusBar) self.tabwidget.addTab(self.afficheurImgDestination, _(u'Visualisation des images créées')) # 5. Création du tab Info self.Logs = QTextEdit() # Zone d'affichage des infos (Zone acceptant le formatage HTML et avec assenceur automatique si nécessaire) self.tabwidget.addTab(self.Logs, _(u'Infos')) #----------------- # widgets du bas #----------------- # Boutons boutAide=QPushButton(_(u" Aide")) boutAide.setIcon(QIcon("Icones/icone_aide_128.png")) self.connect(boutAide, SIGNAL("clicked()"), self.afficherAide) self.boutAppliquer=QPushButton(_(u"Appliquer")) self.boutAppliquer.setIcon(QIcon("Icones/icone_appliquer_128.png")) # Bouton inactif au départ self.boutAppliquer.setEnabled(False) self.connect(self.boutAppliquer, SIGNAL("clicked()"), self.appliquer) # Ligne de séparation juste au dessus des boutons ligne = QFrame() ligne.setFrameShape(QFrame.HLine) ligne.setFrameShadow(QFrame.Sunken) hbox=QHBoxLayout() hbox.addWidget(boutAide) hbox.addStretch() hbox.addWidget(self.boutAppliquer) vbox.addWidget(self.tabwidget) vbox.addWidget(ligne) vbox.addLayout(hbox) self.setLayout(vbox)
def __init__(self, geometry): QWidget.__init__(self) # Boite d'alignement vertical vbox=QVBoxLayout(self) #=== Création des répertoires temporaires ===# # Gestion du repertoire tmp avec EkdConfig self.repTampon = EkdConfig.getTempDir() + os.sep + "tampon" + os.sep + "image_divers_renommage" + os.sep if os.path.isdir(self.repTampon) is False: os.makedirs(self.repTampon) # Au cas où le répertoire existait déjà et qu'il n'était pas vide # -> purge (simple précausion) for toutRepCompo in glob.glob(self.repTampon+'*.*'): os.remove(toutRepCompo) #=== Drapeaux ===# # Une conversion (même partielle) a-t-elle eu lieu après le chargement des images? (1: vrai) # Est-ce que des images sources ont été modifiées? (c'est-à-dire ajoutées ou supprimées) self.modifImageSource = 0 #=== Variable de configuration ===# # Fonctions communes à plusieurs cadres du module Image self.base = Base() # Gestion de la configuration via EkdConfig # Paramètres de configuration self.config = EkdConfig # Identifiant du cadre self.idSection = "image_renommer" # Log du terminal self.base.printSection(self.idSection) # Fonction appelant la fenêtre principale self.mainWindowFrameGeometry = geometry self.listeImgSource = [] # là où s'afficheront les infos self.zoneTexte = QTextEdit("") if PYQT_VERSION_STR < "4.1.0": self.zoneTexte.setText = self.zoneTexte.setPlainText self.zoneTexte.setReadOnly(True) self.tabwidget=QTabWidget() # Boîte de combo self.comboClassement=QComboBox() self.listeComboClassement=[(_(u'Par ordre de sélection'), 'ord_select'),\ (_(u'Par ordre alpha-numérique'), 'ord_apha_num'),\ (_(u'Par ordre de prise de vue (données Exif): ordre croissant'), 'ord_exif_oc'),\ (_(u'Par ordre de prise de vue (données Exif): ordre décroissant'), 'ord_exif_dc')] # Insertion de l'ordre de classement des images/photos dans la combo box for i in self.listeComboClassement: self.comboClassement.addItem(i[0],QVariant(i[1])) self.connect(self.comboClassement, SIGNAL("currentIndexChanged(int)"), self.classement) #=== 1er onglet ===# self.framNbreImg=QFrame() vboxReglage=QVBoxLayout(self.framNbreImg) self.grid = QGridLayout() self.grid.addWidget(QLabel(_(u"Traitement à partir de l'image (numéro)")), 0, 0) self.spin1=SpinSlider(1, 10000, 1, '', self) self.grid.addWidget(self.spin1, 0, 1) self.connect(self.spin1, SIGNAL("valueChanged(int)"), self.changeValNbreImg_1) self.grid.addWidget(QLabel(_(u"Nombre de chiffres après le nom de l'image")), 1, 0) self.spin2=SpinSlider(3, 18, 6, '', self) self.grid.addWidget(self.spin2, 1, 1) self.connect(self.spin2, SIGNAL("valueChanged(int)"), self.changeValNbreImg_1) # Demandé par Marc de la liste lprod # "J'aurais été interressé par un paramètre supplémentaire pour définir l'incrément (ou le pas ...). # En fait cela permettrait d'insérer (donc classer) facilement les photos issues d'appareils # différents mais traitant les mêmes sujets. En fait, je voudrais réaliser une série finale des # photos prises avec mon APN, mon camescope et l'APN de mon épouse. Je numérote de 10 en 10 la # plus grosse série de photos et viens insérer les autres au bon endroit sans avoir à utiliser # des indices a, b, c, etc." self.grid.addWidget(QLabel(_(u"Valeur d'incrément (passage d'une image à l'autre)")), 2, 0) #self.spin3=SpinSlider(1, 1000, 1, 'increment', self) # Ne fonctionne pas self.spin3=SpinSlider(1, 1000, 1, '', self) self.grid.addWidget(self.spin3, 2, 1) self.connect(self.spin3, SIGNAL("valueChanged(int)"), self.changeValNbreImg_1) self.grid.addWidget(QLabel(_(u"Classement")), 3, 0) self.grid.addWidget(self.comboClassement, 3, 1) self.grid.setAlignment(Qt.AlignHCenter) vboxReglage.addLayout(self.grid) vboxReglage.addStretch() #=== 2ème onglet ===# # infos - logs self.zoneAffichInfosImg = QTextEdit("") if PYQT_VERSION_STR < "4.1.0": self.zoneAffichInfosImg.setText = self.zoneAffichInfosImg.setPlainText self.zoneAffichInfosImg.setReadOnly(True) self.framImg=QFrame() vboxReglage=QVBoxLayout(self.framImg) vboxReglage.addWidget(self.zoneAffichInfosImg) self.framImg.setEnabled(False) # Là où s'afficheront les images self.afficheurImgSource=SelectWidget(geometrie = self.mainWindowFrameGeometry) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "image" # Défini le type de fichier source. self.typeSortie = "image" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurImgSource # Fait le lien avec le sélecteur de fichier source. self.indexTabImgSource = self.tabwidget.addTab(self.afficheurImgSource, _(u'Image(s) source')) self.indexTabReglage=self.tabwidget.addTab(self.framNbreImg, _(u'Réglages')) self.indexTabInfo=self.tabwidget.addTab(self.framImg, _(u'Infos')) vbox.addWidget(self.tabwidget) # ------------------------------------------------------------------- # widgets du bas : ligne + boutons Aide et Appliquer # ------------------------------------------------------------------- # Boutons boutAidetRenomImg=QPushButton(_(u" Aide")) boutAidetRenomImg.setIcon(QIcon("Icones/icone_aide_128.png")) self.connect(boutAidetRenomImg, SIGNAL("clicked()"), self.afficherAide) self.boutApp=QPushButton(_(u" Renommer")) self.boutApp.setIcon(QIcon("Icones/icone_appliquer_128.png")) self.connect(self.boutApp, SIGNAL("clicked()"), self.appliquer) # Ligne de séparation juste au dessus des boutons ligne = QFrame() ligne.setFrameShape(QFrame.HLine) ligne.setFrameShadow(QFrame.Sunken) vbox.addWidget(ligne) vbox.addSpacing(-5) # la ligne doit être plus près des boutons hbox=QHBoxLayout() hbox.addWidget(boutAidetRenomImg) hbox.addStretch() # espace entre les 2 boutons hbox.addWidget(self.boutApp) self.boutApp.setEnabled(False) vbox.addLayout(hbox) # Affichage de la boîte principale self.setLayout(vbox) #---------------------------------------------------------------------------------------------------- # Signal de présence d'images dans ler widget de sélection -> modifie le statut des boutons d'action #---------------------------------------------------------------------------------------------------- self.connect(self.afficheurImgSource, SIGNAL("pictureChanged(int)"), self.modifBoutonsAction)
def __init__(self, geometry): QWidget.__init__(self) # Boite d'alignement vertical vbox=QVBoxLayout(self) # Est-ce que l'image a déjà été affichée une fois ? # -> gain de temps au niveau de l'affichage self.drapeauImage = 0 # 0: non ; 1: oui # Fonctions communes à plusieurs cadres du module Image self.base = Base() # Paramètres de configuration self.config = EkdConfig self.idSection = "image_divers_infos" # Log du terminal self.base.printSection(self.idSection) # Fonction appelant la fenêtre principale self.mainWindowFrameGeometry = geometry self.chemin = [] # Là où s'afficheront les images self.afficheurImgSource=SelectWidget(geometrie = self.mainWindowFrameGeometry) # ----------- # Onglets # ----------- # Onglets self.tab = QTabWidget() #== onglet chargement d'images ===# self.indexTabImgSource = self.tab.addTab(self.afficheurImgSource, _(u'Image(s) source')) #== onglet d'information textuelle ===# self.zoneTexte = QTextEdit("") if PYQT_VERSION_STR < "4.1.0": self.zoneTexte.setText = self.zoneTexte.setPlainText self.zoneTexte.setReadOnly(True) frame = QFrame() hboxTab = QHBoxLayout(frame) hboxTab.addWidget(self.zoneTexte) self.tabIndexText = self.tab.addTab(frame, _("Infos Texte")) #== onglet d'information exif ===# self.table = QTableWidget() self.tabIndexExif = self.tab.addTab(self.table, _("Infos Exif")) vbox.addWidget(self.tab) # ------------------------------------------------------------------- # widgets du bas : ligne + boutons # ------------------------------------------------------------------- # boutons boutAide=QPushButton(_(u" Aide")) boutAide.setIcon(QIcon("Icones/icone_aide_128.png")) self.connect(boutAide, SIGNAL("clicked()"), self.afficherAide) self.boutSauverInfos=QPushButton(_(u" Sauver infos (.txt)")) # Show infos (.txt) ==> Sauver infos (.txt) self.boutSauverInfos.setIcon(QIcon("Icones/icone_appliquer_128.png")) self.boutSauverInfos.setEnabled(False) self.connect(self.boutSauverInfos, SIGNAL("clicked()"), self.sauverInfos) self.boutSauverExif=QPushButton(_(u" Sauver Exif (.html)")) # Save Exif (.html) ==> Sauver Exif (.html) self.boutSauverExif.setIcon(QIcon("Icones/icone_appliquer_128.png")) self.boutSauverExif.setEnabled(False) self.connect(self.boutSauverExif, SIGNAL("clicked()"), self.sauverExif) # ligne de séparation juste au dessus des boutons ligne = QFrame() ligne.setFrameShape(QFrame.HLine) ligne.setFrameShadow(QFrame.Sunken) vbox.addWidget(ligne) vbox.addSpacing(-5) # la ligne doit être plus près des boutons hbox=QHBoxLayout() hbox.addWidget(boutAide) hbox.addStretch() # espace entre les 2 boutons hbox.addWidget(self.boutSauverInfos) hbox.addWidget(self.boutSauverExif) vbox.addLayout(hbox) # affichage de la boîte principale self.setLayout(vbox) #---------------------------------------------------------------------------------------------------- # Signal de présence d'images dans ler widget de sélection -> appelle la fonction appliquer #---------------------------------------------------------------------------------------------------- self.connect(self.afficheurImgSource, SIGNAL("pictureChanged(int)"), self.modifImgSource) self.connect(self.afficheurImgSource, SIGNAL("pictureSelected()"), self.appliquer)
class Animation_MontagVideoVidSeul(Base): """ ----------------------------------------- # Cadre accueillant les widgets de : # Animation >> Montage vidéo >> Vidéo seulement # -----------------------------------------""" def __init__(self, statusBar): # ------------------------------- # Parametres généraux du widget # ------------------------------- #=== tout sera mis dans une boîte verticale ===# vbox=QVBoxLayout() #=== Variable de configuration ===# self.config=EkdConfig # Identifiant de la classe self.idSection = "animation_montage_video_seul" super(Animation_MontagVideoVidSeul, self).__init__(boite='vbox', titre=_(u"Montage: Vidéo seulement")) self.printSection() # Création des répertoires temporaires self.repTampon = EkdConfig.getTempDir() + os.sep if os.path.isdir(self.repTampon) is False: os.makedirs(self.repTampon) # Au cas où le répertoire existait déjà et qu'il n'était pas vide -> purge (simple précausion) for toutRepCompo in glob.glob(self.repTampon+'*.*'): os.remove(toutRepCompo) # Liste de fichiers initiaux (contenu dans le fichier de configuration) self.lstFichiersSource = [] self.lstFichiersSourceProv = [] # idem mais provisoire (sert à récupérer les chemins dans l'ordre de sélection) self.statusBar = statusBar #------------------------------------------------------------------- self.afficheurVideoSource=SelectWidget(extensions = ["*.avi", "*.mpg", "*.mpeg", "*.mjpeg", "*.flv", "*.mp4", "*.h264", "*.dv", "*.vob"], mode="texte", video = True) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "video" # Défini le type de fichier source. self.typeSortie = "video" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurVideoSource # Fait le lien avec le sélecteur de fichier source. ##################################################################### # Onglets self.indexVideoSource = self.add(self.afficheurVideoSource, _(u'Video(s) source')) self.connect(self.afficheurVideoSource,SIGNAL("fileSelected"),self.getFile) self.connect(self.afficheurVideoSource, SIGNAL("pictureChanged(int)"), self.getFile) #-------------------------------------------------------------------- self.addReglage(boite="vbox") #-------------------------------------------------------------------- #=== Widget qui seront inclus dans la boite de réglage ===# self.ordreVideo = selectJoinMultipleSound(0, self) self.ordreVideo.setTitleAndTips(_(u"Fichiers vidéos à joindre"), _(u"Liste des fichiers vidéo à joindre. <b>Pour information, vous pouvez monter et descendre les fichiers grâce aux flèches haut et bas (les fichiers apparaissant en haut de la liste sont ceux qui seront au début du montage)</b>")) self.layoutReglage.addWidget(self.ordreVideo) # ---------------------------- # Boite de groupe de mplayer # ---------------------------- self.addPreview() self.addLog() def getFile(self): ''' # On utilise la nouvelle interface de récupération des vidéos Récupération de la vidéo source selectionnée ''' self.chemin = self.afficheurVideoSource.getFile() self.lstFichiersSource = self.afficheurVideoSource.getFiles() self.ordreVideo.addSoundAction(self.lstFichiersSource) self.ordreVideo.delFile(self.lstFichiersSource) self.boutApp.setEnabled(True) self.mplayer.setEnabled(True) self.mplayer.setVideos([self.chemin]) self.radioSource.setChecked(True) self.radioSource.setEnabled(True) self.emit(SIGNAL("loaded")) return self.chemin def listeChemins(self, ch): """transforme une chaine de caractères en liste de chemins""" lst = ch.split("'") for i in lst: # i: élément de la ligne if (',' in i) or ('[' in i) or (']' in i): lst.remove(i) return lst def fctRadioSource(self, bool=None): """"Communique la vidéo appropriée à mplayer""" if bool: self.mplayer.listeVideos = self.lstFichiersSource try : self.radioConvert.setChecked(False) except : None def fctRadioConvert(self, bool=None): """"Communique la vidéo appropriée à mplayer""" if bool: self.mplayer.listeVideos = self.lstFichiersSortie try : self.radioSource.setChecked(False) except : None def ouvrirSource(self, nomEntree=None): """ Récupération des chemins vidéo sélectionnée """ chemin = self.recupSources(nomEntree) if not chemin: return self.lstFichiersSource = [] for fichier in chemin: self.lstFichiersSource.append(fichier) self.lstFichiersSourceProv = self.lstFichiersSource[:] # ':' car sinon on créé un alias if len(self.lstFichiersSourceProv)>1: self.boutApp.setEnabled(True) self.boutApp.setToolTip("") else: self.boutApp.setEnabled(False) self.boutApp.setToolTip(_(u"Veuillez sélectionner au moins 2 vidéos")) self.mplayer.setEnabled(True) self.mplayer.listeVideos = self.lstFichiersSource self.radioSource.setChecked(True) self.radioSource.setEnabled(False) self.radioConvert.setEnabled(False) self.statusBar.showMessage(_(u"La vidéo résultante ne pourra pas être lue avec tous les logiciels")) def afficherAide(self): """ Boîte de dialogue de l'aide """ super(Animation_MontagVideoVidSeul, self).afficherAide(_(u"<p><b>Sous le terme de montage vidéo, vous pouvez ici assembler des vidéos pour en constituer une seule. EKD peut assembler des vidéos de diférentes nature (extensions).</b></p><p><b>Il est à noter ici (pour le Montage vidéo) que vous bénéficiez d'un traitement par lot, c'est à dire que tous les fichiers que vous allez charger seront traités (et pas seulement le fichier sélectionné).</b></p><p>Dans l'onglet <b>'Vidéo(s) source'</b> cliquez sur le bouton <b>Ajouter</b>, une boîte de dialogue apparaît, sur la partie gauche sélectionnez le répertoire (au besoin dépliez les sous-répertoires), allez chercher la/les vidéo(s). Si vous voulez sélectionner plusieurs vidéos d'un coup, maintenez la touche <b>CTRL</b> (ou <b>SHIFT</b>) du clavier enfoncée (tout en sélectionnant vos vidéos), cliquez sur <b>Ajouter</b>.</p><p>Vous pouvez dès lors sélectionner une vidéo dans la liste et la visionner (par le bouton juste à la droite de cette liste), vous noterez que vous pouvez visionner la vidéo en quatre tiers, en seize neuvième ou avec les proportions d'origine de la vidéo (w;h). De même si vous le désirez, vous pouvez obtenir des informations complètes sur la vidéo sélectionnée, et ce par le bouton <b>'Infos'</b> (en bas).</p><p>Dans l'onglet <b>Réglages</b>, vous pouvez changer l'ordre de montage des fichiers et ce en remontant ou en redéscendant (par les flèches haut et bas) dans la liste des <b>'Fichiers vidéo à joindre'</b>.</p><p>Une fois tout ceci fait, cliquez sur le bouton <b>'Appliquer'</b>, sélectionnez le répertoire de sauvegarde, indiquez votre <b>'Nom de fichier'</b>, cliquez sur le bouton <b>'Enregistrer'</b> et attendez le temps de la conversion.</p><p>Dans l'onglet <b>'Visionner vidéo'</b> vous pouvez visionner le résultat (avant la concaténation) en sélectionnant <b>'vidéo(s) source(s)'</b>, après la concaténation <b>'vidéo convertie'</b>.</p><p>L'onglet <b>'Infos'</b> vous permet de voir les vidéos chargées (avec leurs chemins exacts) avant et après conversion.</p>")) def stat_dim_video(self): """Calcul statistique des dimensions des vidéos les plus présentes dans le lot""" from gui_modules_animation.infoVideo import infovideo listePrepaRedim = [] # Détection des dimensions différentes (résolutions) # dans les vidéos chargées par l'utilisateur for parcVideoResolution in self.lstFichiersSource: info = infovideo(parcVideoResolution) listePrepaRedim.append((info.videoLargeur, info.videoHauteur)) # Merci beaucoup à Marc Keller de la liste: python at aful.org de m'avoir # aidé pour cette partie (les 4 lignes en dessous) dictSeq={}.fromkeys(listePrepaRedim, 0) for cle in listePrepaRedim: dictSeq[cle]+=1 self.lStatDimSeq=sorted(zip(dictSeq.itervalues(), dictSeq.iterkeys()), reverse=1) self.dimStatVideo=self.lStatDimSeq[0][1] ''' print print "Toutes les dimensions des vidéos (avec le nbre de vidéos):", self.lStatDimSeq print 'Dimension des vidéos la plus presente dans la sequence:', self.dimStatVideo print "Nombre de tailles de vidéos différentes dans le lot :", len(self.lStatDimSeq) print ''' EkdPrint(u'') EkdPrint(u"Toutes les dimensions des vidéos (avec le nbre de vidéos): %s" % self.lStatDimSeq) EkdPrint(u'Dimension des vidéos la plus presente dans la sequence: %s' % str(self.dimStatVideo)) EkdPrint(u"Nombre de tailles de vidéos différentes dans le lot: %s" % len(self.lStatDimSeq)) EkdPrint(u'') if len(self.lStatDimSeq)>1: return 0 else: return 1 ######################################################################################### def stat_codec_video(self): """Calcul detecte si une vidéo a un codec différent dans le lot""" from gui_modules_animation.infoVideo import infovideo listeCodec = {} for video in self.lstFichiersSource: info = infovideo(video) try: listeCodec[info.video_codec] = listeCodec[info.video_codec] + 1 except KeyError: listeCodec[info.video_codec] = 1 #print "Ensemble des codecs detectés : %s " % listeCodec.keys() EkdPrint(u"Ensemble des codecs détectés : %s " % listeCodec.keys()) self.codecStatVideo = listeCodec def appliquer(self, nomSortie=None, ouvert=1): """Fusion de vidéos""" self.lstFichiersSource = self.ordreVideo.getListFile() self.stat_dim_video() resolution = self.dimStatVideo self.stat_codec_video() # Chemin du répertoire temporaire rep_video_ext_resol = self.repTampon + 'video_extension_resol' + os.sep chemin=unicode(self.ordreVideo.getListFile()) if not nomSortie: suffix = '.avi' ############################################################################## saveDialog = EkdSaveDialog(self, mode="video", suffix=suffix, title=_(u"Sauver")) cheminFichierEnregistrerVideo = saveDialog.getFile() ################################################################################################################### else: # module séquentiel cheminFichierEnregistrerVideo=nomSortie if not cheminFichierEnregistrerVideo: return fichiersSource=self.lstFichiersSource if os.path.isdir(rep_video_ext_resol) is False: os.makedirs(rep_video_ext_resol) ## Dans tous les cas on change le format (mjpeg/avi) ## # Si la liste contient plus d'un élément, c'est à dire si elle contient des ## # extensions différentes et si la résolution des vidéos est différente ########################################################################################################### wmcer = WidgetMEncoderConcatExtResol(self.lstFichiersSource, valeurNum=resolution) wmcer.setWindowTitle(_(u"Traitement vidéo (extension et résolution)")) wmcer.exec_() # Les vidéos chargées en vue de la concaténation sont maintenant ds # le rep tampon .../video_extension_resol self.lstFichiersSource = glob.glob(unicode(rep_video_ext_resol+'*.*')) # Mise en ordre (numérotation) car cela peut # poser problème au moment de la concaténation #print "Debug:: Files to mix : ", self.lstFichiersSource EkdPrint(u"Debug:: Files to mix : %s" % self.lstFichiersSource) self.lstFichiersSource.sort() ### Concaténation élégante ######################################################### cheminVideoProv = self.repTampon + "video" + suffix # on écrit dans "output" (descripteur de fichier de cheminVideoProv) try: output = open(cheminVideoProv, 'wb+') for cheminFichier in self.lstFichiersSource: input = open(cheminFichier, 'rb') output.write(input.read()) input.close() output.close() except Exception, details: #print "**** DEBUG: Erreur dans l'ouverture du fichier : " , details EkdPrint(u"**** DEBUG: Erreur dans l'ouverture du fichier : %s" % details) # Maintenant cheminVideoProv contient la concaténation de tous fichiers ########################################################################################################### #=== Commande de concaténation finale ===# try: mencoder = WidgetMEncoder('fusion_video', cheminVideoProv, cheminFichierEnregistrerVideo, laisserOuvert=ouvert) mencoder.setWindowTitle(_(u"Fusion des fichiers vidéos")) mencoder.exec_() except: messageErrAnEnc=QMessageBox(self) messageErrAnEnc.setText(_(u"Problème lors de l'étape finale de concaténation vidéo (mencoder)\n")) messageErrAnEnc.setWindowTitle(_(u"Erreur")) messageErrAnEnc.setIcon(QMessageBox.Warning) messageErrAnEnc.exec_() os.remove(cheminVideoProv) return # Suppression du fichier temporaire os.remove(cheminVideoProv) self.lstFichiersSortie = cheminFichierEnregistrerVideo # pour la boite de dialogue de comparaison self.radioConvert.setChecked(True) self.radioSource.setEnabled(True) self.radioSource.setChecked(False) self.radioConvert.setEnabled(True) ### Ajouté le 04/09/2009 pour donner les infos à l'utilisateur sur ce qui a été traité. self.infoLog(None, fichiersSource, None, cheminFichierEnregistrerVideo) return self.lstFichiersSortie # module séquentiel
def __init__(self, titre=_(u"Titre")): # ------------------------------- # Parametres généraux du widget # ------------------------------- self.printSection() # tout sera mis dans une boîte verticale self.vbox=QVBoxLayout() super(Base_EncodageFiltre, self).__init__(boite='vbox', titre=titre) ## ------------------------------------------------------------------- ## on utilise le selecteur d'image pour les vidéos from gui_modules_image.selectWidget import SelectWidget # Là où on naviguera entre les fichiers self.afficheurVideoSource=SelectWidget(mode="texte", video = True) # Onglets self.indexVideoSource = self.add(self.afficheurVideoSource, _(u'Video(s) source')) self.connect(self.afficheurVideoSource,SIGNAL("fileSelected"),self.getFile) self.connect(self.afficheurVideoSource, SIGNAL("pictureChanged(int)"), self.getFile) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "video" # Défini le type de fichier source. self.typeSortie = "video" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurVideoSource # Fait le lien avec le sélecteur de fichier source. #=== Widget qui seront inclus dans la boite de réglage ===# # Boite de combo self.combo=QComboBox() # Le self est nécessaire pour récupérer l'identifiant # de l'entrée de la boite de combo # Insertion des codecs de compression dans la combo box # Rq: listeCombo est de la forme: [(clé QVariant, texte à afficher,...), ...] for i in self.listeCombo: # self obligatoire pour le module séquentiel self.combo.addItem(i[1], QVariant(i[0])) self.connect(self.combo,SIGNAL("currentIndexChanged(int)"),self.changerItemQStacked) # Utilisation de EkdConfig : modification des sections if self.idSection == "animation_filtresvideo": # On donne la référence de la boite de combo au widget de découpage # pour que ce dernier connaisse le type de découpage sélectionné (assisté ou libre) self.filtreDecouper.setComboParent(self.combo) # Widgets intégrés à la boîte de groupe "Réglage de sortie de l'encodage". # La boîte de combo définie ci-dessus 'sélectionne' ceux qui vont s'afficher # -> utilisation d'un stacked BoxReglage = QGroupBox() layoutReglage = QVBoxLayout(BoxReglage) layoutReglage.addWidget(self.combo) layoutReglage.addWidget(self.stacked) ### self.loadOptions() self.add(BoxReglage, _(u"Réglages")) self.addPreview() self.addLog()
def __init__(self, statusBar): # ------------------------------- # Parametres généraux du widget # ------------------------------- #=== tout sera mis dans une boîte verticale ===# vbox=QVBoxLayout() #=== Variable de configuration ===# self.config=EkdConfig # Identifiant de la classe self.idSection = "animation_montage_video_seul" super(Animation_MontagVideoVidSeul, self).__init__(boite='vbox', titre=_(u"Montage: Vidéo seulement")) self.printSection() # Création des répertoires temporaires self.repTampon = EkdConfig.getTempDir() + os.sep if os.path.isdir(self.repTampon) is False: os.makedirs(self.repTampon) # Au cas où le répertoire existait déjà et qu'il n'était pas vide -> purge (simple précausion) for toutRepCompo in glob.glob(self.repTampon+'*.*'): os.remove(toutRepCompo) # Liste de fichiers initiaux (contenu dans le fichier de configuration) self.lstFichiersSource = [] self.lstFichiersSourceProv = [] # idem mais provisoire (sert à récupérer les chemins dans l'ordre de sélection) self.statusBar = statusBar #------------------------------------------------------------------- self.afficheurVideoSource=SelectWidget(extensions = ["*.avi", "*.mpg", "*.mpeg", "*.mjpeg", "*.flv", "*.mp4", "*.h264", "*.dv", "*.vob"], mode="texte", video = True) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "video" # Défini le type de fichier source. self.typeSortie = "video" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurVideoSource # Fait le lien avec le sélecteur de fichier source. ##################################################################### # Onglets self.indexVideoSource = self.add(self.afficheurVideoSource, _(u'Video(s) source')) self.connect(self.afficheurVideoSource,SIGNAL("fileSelected"),self.getFile) self.connect(self.afficheurVideoSource, SIGNAL("pictureChanged(int)"), self.getFile) #-------------------------------------------------------------------- self.addReglage(boite="vbox") #-------------------------------------------------------------------- #=== Widget qui seront inclus dans la boite de réglage ===# self.ordreVideo = selectJoinMultipleSound(0, self) self.ordreVideo.setTitleAndTips(_(u"Fichiers vidéos à joindre"), _(u"Liste des fichiers vidéo à joindre. <b>Pour information, vous pouvez monter et descendre les fichiers grâce aux flèches haut et bas (les fichiers apparaissant en haut de la liste sont ceux qui seront au début du montage)</b>")) self.layoutReglage.addWidget(self.ordreVideo) # ---------------------------- # Boite de groupe de mplayer # ---------------------------- self.addPreview() self.addLog()
def __init__(self, parent): # ------------------------------- # Parametres généraux du widget # ------------------------------- self.config=EkdConfig self.parent = parent #=== Identifiant de la classe ===# self.idSection = "son_normaliser_convertir_musique_ou_son" super(MusiqueSon_normalize, self).__init__(None, None, None, 'vbox') # Base module de animation self.setTitle(_(u"Normaliser et convertir un fichier audio")) self.printSection() #------------------------------------------------------------------------ # TabWidget pour les réglages et pour l'écoute du résultat #------------------------------------------------------------------------ extFormat=[] for fmt in self.parent.soxSuppFormat : extFormat.append("*."+fmt) # Widget standard de sélection de fichier audio dans le tab standard self.selectionAudioFile = SelectWidget(extensions = extFormat, mode="texte", audio = True) # Onglets self.tab.insertTab(0,self.selectionAudioFile, _(u'Son(s) source')) self.connect(self.selectionAudioFile,SIGNAL("fileSelected"),self.synchroFiles) self.connect(self.selectionAudioFile, SIGNAL("pictureChanged(int)"), self.synchroFiles) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "audio" # Défini le type de fichier source. self.typeSortie = "audio" # Défini le type de fichier de sortie. self.sourceEntrees = self.selectionAudioFile # Fait le lien avec le sélecteur de fichier source. # ------------------------------------------------------------------- # Sélection et affichage des fichiers à joindre : "Fichiers audio source" # ------------------------------------------------------------------- reglage = QWidget() regVLayout = QVBoxLayout(reglage) self.selectionFile = choixSonSortieWidget(self.parent.soxSuppFormat, parent=self) regVLayout.addWidget(self.selectionFile) niveau = QGroupBox(_(u"Choix du niveau")) self.choix1 = QRadioButton(_(u"- 3 dB")) self.choix2 = QRadioButton(_(u"- 6 dB")) self.choix3 = QRadioButton(_(u"- 9 dB")) self.choix1.setChecked(True) nivLayout = QHBoxLayout() nivLayout.addWidget(self.choix1) nivLayout.addWidget(self.choix2) nivLayout.addWidget(self.choix3) niveau.setLayout(nivLayout) regVLayout.addWidget(niveau) mode = QGroupBox(_(u"Choix du mode de normalisation")) self.choixm1 = QRadioButton(_(u"Standard")) self.choixm2 = QRadioButton(_(u"Individuel")) self.choixm3 = QRadioButton(_(u"Balancé")) self.choixm1.setChecked(True) modeLayout = QHBoxLayout() modeLayout.addWidget(self.choixm1) modeLayout.addWidget(self.choixm2) modeLayout.addWidget(self.choixm3) mode.setLayout(modeLayout) regVLayout.addWidget(mode) regVLayout.addStretch() self.tab.addTab(reglage,_(u"Réglage")) #------------------------------------------------------------------------ # Ecoute du résultat #------------------------------------------------------------------------ #=== Widgets mplayer ===# widget = QWidget() vboxMplayer = QVBoxLayout(widget) vboxMplayer.addStretch() hbox = QHBoxLayout() vboxMplayer.addLayout(hbox) hbox.addStretch() self.mplayer=Mplayer(taille=(300,270), facteurLimitant=Mplayer.LARGEUR, choixWidget=(Mplayer.PAS_PRECEDENT_SUIVANT,Mplayer.CURSEUR_A_PART)) self.mplayer.setAudio(True) hbox.addWidget(self.mplayer) hbox.addStretch() vboxMplayer.addStretch() self.mplayer.setEnabled(False) self.tab.addTab(widget, _(u"Son créé")) ### pour les infos supplémentaires self.addLog()
class MusiqueSon_Join(Base): # ----------------------------------- # Cadre accueillant les widgets de : # Musique-Son >> Encodage # ----------------------------------- def __init__(self, parent): #=== Variable de configuration ===# self.config=EkdConfig self.parent = parent #=== Identifiant de la classe ===# self.idSection = "son_joindre_multiple_fichier_audio" super(MusiqueSon_Join, self).__init__(None, None, None, 'vbox') # module de animation self.setTitle(_(u"Joindre plusieurs fichiers audio")) self.printSection() #------------------------------------------------------------------------ # TabWidget pour les réglages et pour l'écoute du résultat #------------------------------------------------------------------------ extFormat=[] for fmt in self.parent.soxSuppFormat : extFormat.append("*."+fmt) # Widget standard de sélection de fichier audio dans le tab standard self.selectionAudioFile = SelectWidget(extensions = extFormat, mode="texte", audio = True) # Onglets self.tab.insertTab(0,self.selectionAudioFile, _(u'Son(s) source')) self.connect(self.selectionAudioFile,SIGNAL("fileSelected"),self.synchroFiles) self.connect(self.selectionAudioFile, SIGNAL("pictureChanged(int)"), self.synchroFiles) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "audio" # Défini le type de fichier source. self.typeSortie = "audio" # Défini le type de fichier de sortie. self.sourceEntrees = self.selectionAudioFile # Fait le lien avec le sélecteur de fichier source. # ------------------------------------------------------------------- # Sélection et affichage des fichiers à joindre : "Fichiers audio source" # ------------------------------------------------------------------- self.selectionFile = selectJoinMultipleSound(exp=1, parent=self) self.tab.addTab(self.selectionFile,_(u"Réglage")) #------------------------------------------------------------------------ # Ecoute du résultat #------------------------------------------------------------------------ #=== Widgets mplayer ===# widget = QWidget() vboxMplayer = QVBoxLayout(widget) vboxMplayer.addStretch() hbox = QHBoxLayout() vboxMplayer.addLayout(hbox) hbox.addStretch() self.mplayer=Mplayer(taille=(300,270), facteurLimitant=Mplayer.LARGEUR, choixWidget=(Mplayer.PAS_PRECEDENT_SUIVANT,Mplayer.CURSEUR_A_PART))# self.mplayer.setAudio(True) hbox.addWidget(self.mplayer) hbox.addStretch() vboxMplayer.addStretch() self.mplayer.setEnabled(False) self.tab.addTab(widget, _(u"Son créé")) ### report d'informations à la fin du process self.addLog() # ------------------------------------------------------------------- # widgets du bas : ligne + boutons Aide et Appliquer # ------------------------------------------------------------------- # Aide def synchroFiles(self): ''' # On utilise la nouvelle interface de récupération des fichiers audio ''' chemins = self.selectionAudioFile.getFiles() self.selectionFile.addSoundAction(chemins) self.selectionFile.delFile(chemins) def encodAudioWav(self, nfile, cheminAudioSource, cheminAudioSorti) : """ Joindre et encoder les fichiers audio """ if self.selectionFile.choixFormatAudio.reglageExp.getExpertState() : regExp = self.selectionFile.choixFormatAudio.reglageExp.getC() else : regExp = u"" self.process = soxProcessMulti(cheminAudioSource, cheminAudioSorti, regExp, self.parent) self.process.show() self.process.run() self.connect(self.process,SIGNAL("endProcess"),self.endProcess) def endProcess(self, sortie) : self.mplayer.setEnabled(True) self.mplayer.listeVideos = sortie self.tab.setCurrentIndex(2) ### Info sur le traitement effectué. self.infoLog(None, None, self.selectionFile.getListFile(), sortie) def afficherAide(self): """ Boîte de dialogue de l'aide """ self.aide = EkdAide(550,400,self) self.aide.setText(_(u"<p><b>Ce module permet de joindre plusieurs fichiers son à la suite dans un fichier de sortie. EKD peut assembler des fichiers audio de diférentes nature (extensions).</b></p><p><b>Il est à noter ici que vous bénéficiez d'un traitement par lot, c'est à dire que tous les fichiers que vous allez charger seront traités (et pas seulement le fichier sélectionné).</b></p><p>Dans l'onglet <b>Son(s) source</b> cliquez sur le bouton <b>Ajouter</b>, une boîte de dialogue apparaît, sur la partie gauche sélectionnez le répertoire (au besoin dépliez les sous-répertoires), allez chercher la/les fichiers(s) audio. Si vous voulez sélectionner plusieurs fichiers audio d'un coup, maintenez la touche <b>CTRL</b> (ou <b>SHIFT</b>) du clavier enfoncée (tout en sélectionnant vos fichiers audio), cliquez sur <b>Ajouter</b>.</p><p>Vous pouvez dès lors sélectionner un fichier audio dans la liste et le lire (par le bouton juste à la droite de cette liste). De même si vous le désirez, vous pouvez obtenir des informations complètes sur le fichier audio sélectionné, et ce par le bouton <b>Infos</b> (en bas).</p><p>Dans l'onglet <b>Réglages</b> sélectionnez le <b>Format du fichier de sortie</b>, et s'il le faut <b>Réglage expert</b> et faites éventuellement le réglage de la <b>Qualité d'encodage</b>. Dans ce même onglet vous pouvez changer l'ordre de montage des fichiers et ce en remontant ou en redéscendant (par les flèches haut et bas) dans la <b>Liste des fichiers à joindre</b>. Pour information les fichiers apparaissant en haut de la liste sont ceux qui seront au début du montage.</p><p>Une fois tout ceci fait, cliquez sur le bouton <b>Appliquer</b>, sélectionnez le répertoire de sauvegarde, indiquez votre <b>Nom de fichier</b>, cliquez sur le bouton <b>Enregistrer</b> et attendez le temps de la conversion. A la fin du traitement cliquez sur le bouton <b>Fermer</b> de la fenêtre <b>Affichage de la progression</b>.</p><p>Dans l'onglet <b>Son créé</b> vous pouvez lire le résultat.</p><p>L'onglet <b>Infos</b> vous permet de voir les fichiers audio chargés (avec leurs chemins exacts) avant et après conversion.</p>")) self.aide.show() def appliquer(self): """ appelle la boite de dialogue de sélection de fichier à sauver """ #=== Détermination des chemins d'entrée et sortie ===# # récupération du chemin du fichier audio de destination (à l'exception de l'extension) suffixSortie = u"."+self.selectionFile.getFileExt() saveDialog = EkdSaveDialog(self, mode="audio", suffix=suffixSortie, title=_(u"Sauver")) self.cheminAudioSorti = saveDialog.getFile() if not self.cheminAudioSorti: return # récupération du nombre de fichier audio à joindre nfile = self.selectionFile.getNumFile() # Encodage self.encodAudioWav(nfile, self.selectionFile.getListFile(), self.cheminAudioSorti) def load(self) : self.selectionAudioFile.loadFileLocation(self.idSection) self.selectionFile.choixFormatAudio.reglageExp.loadConfig(self.idSection) files = eval(EkdConfig.get(self.idSection, 'ordre')) #print "[Debug] Chargement des fichiers |",files,"| de la section :",idsection self.selectionFile.setListSource(files) def save(self) : self.selectionAudioFile.saveFileLocation(self.idSection) self.selectionFile.choixFormatAudio.reglageExp.saveConfig(self.idSection) files = self.selectionFile.getListFile() if files != None : EkdConfig.set(self.idSection, u'ordre', files)
def __init__(self): # ------------------------------- # Parametres généraux du widget # ------------------------------- vbox=QVBoxLayout() #=== Variable de configuration ===# self.config=EkdConfig #=== Identifiant de la classe ===# self.idSection = "animation_reglages_divers" super(Animation_ReglagesDivers, self).__init__('hbox', titre=_(u"Nombre d'images par seconde")) self.printSection() # ------------------------------------------------------------------- # Boîte de groupe : "Fichier vidéo source" # ------------------------------------------------------------------- self.afficheurVideoSource=SelectWidget(extensions = ["*.avi", "*.dv"], mode="texte", video = True) # Onglets self.indexVideoSource = self.add(self.afficheurVideoSource, _(u'Video(s) source')) self.connect(self.afficheurVideoSource,SIGNAL("fileSelected"),self.getFile) self.connect(self.afficheurVideoSource, SIGNAL("pictureChanged(int)"), self.getFile) #-------------------------------------------------------------------- self.lstFichiersSortie = [] ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "video" # Défini le type de fichier source. self.typeSortie = "video" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurVideoSource # Fait le lien avec le sélecteur de fichier source. # ------------------------------------------------------------------- # Boîte de groupe "Réglage de sortie de l'encodage" # ------------------------------------------------------------------- groupReglage = QGroupBox() self.layoutReglage = QHBoxLayout(groupReglage) ##=== Widget qui seront inclus dans la boite de réglage ===# # boite de spin self.spin=QSpinBox() # self car on va récupérer la variable depuis le moteur self.spin.setRange(1,60) # valeur 22 par défaut self.connect(self.spin,SIGNAL("valueChanged(int)"),self.spinChange) # curseur self.curseur = QSlider(Qt.Horizontal) self.curseur.setRange(1,60) self.connect(self.curseur, SIGNAL("sliderMoved(int)"), self.curseurChange) #=== Chargement du paramètre de configuration ===# try: self.spin.setValue(int(self.config.get(self.idSection,'spin'))) except: self.spin.setValue(22) # info txt2 = _(u"La vidéo source contient") txt3=_(u"img/s") self.info=QLabel("%s x %s" %(txt2,txt3)) self.layoutReglage.addWidget(self.spin) self.layoutReglage.addWidget(self.curseur) self.layoutReglage.addWidget(self.info) self.add(groupReglage, _(u"Réglages")) self.addPreview() self.addLog()
def __init__(self, parent): #=== Variable de configuration ===# self.config=EkdConfig self.parent = parent #=== Identifiant de la classe ===# self.idSection = "son_joindre_multiple_fichier_audio" super(MusiqueSon_Join, self).__init__(None, None, None, 'vbox') # module de animation self.setTitle(_(u"Joindre plusieurs fichiers audio")) self.printSection() #------------------------------------------------------------------------ # TabWidget pour les réglages et pour l'écoute du résultat #------------------------------------------------------------------------ extFormat=[] for fmt in self.parent.soxSuppFormat : extFormat.append("*."+fmt) # Widget standard de sélection de fichier audio dans le tab standard self.selectionAudioFile = SelectWidget(extensions = extFormat, mode="texte", audio = True) # Onglets self.tab.insertTab(0,self.selectionAudioFile, _(u'Son(s) source')) self.connect(self.selectionAudioFile,SIGNAL("fileSelected"),self.synchroFiles) self.connect(self.selectionAudioFile, SIGNAL("pictureChanged(int)"), self.synchroFiles) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "audio" # Défini le type de fichier source. self.typeSortie = "audio" # Défini le type de fichier de sortie. self.sourceEntrees = self.selectionAudioFile # Fait le lien avec le sélecteur de fichier source. # ------------------------------------------------------------------- # Sélection et affichage des fichiers à joindre : "Fichiers audio source" # ------------------------------------------------------------------- self.selectionFile = selectJoinMultipleSound(exp=1, parent=self) self.tab.addTab(self.selectionFile,_(u"Réglage")) #------------------------------------------------------------------------ # Ecoute du résultat #------------------------------------------------------------------------ #=== Widgets mplayer ===# widget = QWidget() vboxMplayer = QVBoxLayout(widget) vboxMplayer.addStretch() hbox = QHBoxLayout() vboxMplayer.addLayout(hbox) hbox.addStretch() self.mplayer=Mplayer(taille=(300,270), facteurLimitant=Mplayer.LARGEUR, choixWidget=(Mplayer.PAS_PRECEDENT_SUIVANT,Mplayer.CURSEUR_A_PART))# self.mplayer.setAudio(True) hbox.addWidget(self.mplayer) hbox.addStretch() vboxMplayer.addStretch() self.mplayer.setEnabled(False) self.tab.addTab(widget, _(u"Son créé")) ### report d'informations à la fin du process self.addLog()
def __init__(self, statusBar, parent=None): vbox=QVBoxLayout() #=== Variable de configuration ===# self.config=EkdConfig # Identifiant de la classe self.idSection = "animation_montage_video_et_audio" super(Animation_MontagVideoVidPlusAudio, self).__init__(boite='vbox', titre=_(u"Montage: Vidéo et audio")) self.printSection() self.repTampon = EkdConfig.getTempDir() + os.sep if os.path.isdir(self.repTampon) is False: os.makedirs(self.repTampon) # Au cas où le répertoire existait déjà et qu'il n'était pas vide -> purge (simple précaution) for toutRepCompo in glob.glob(self.repTampon+'*.*'): os.remove(toutRepCompo) # Liste de fichiers initiaux (contenu dans le fichier de configuration) self.lstFichiersSource = [] self.lstFichiersSourceProv = [] # idem mais provisoire (sert à récupérer les chemins dans l'ordre de sélection) self.lstFichiersSourceAudio = [] self.lstFichiersSourceProvAudio = [] # Par soucis de lisibilité, on préfère ne pas utiliser des objets présent dans le parent de cette façon # mais plutôt le passer en paramètre (le code est plus facile à lire de cette façon) self.statusBar = statusBar #------------------------------------------------------------------- self.afficheurVideoSource=SelectWidget(extensions = ["*.avi", "*.mpg", "*.mpeg", "*.mjpeg", "*.flv", "*.mp4", "*.h264", "*.dv", "*.vob"], mode="texte", video = True) ################################################################################### # Onglets self.indexVideoSource = self.add(self.afficheurVideoSource, _(u'Video(s) source')) self.connect(self.afficheurVideoSource,SIGNAL("fileSelected"),self.getFile) self.connect(self.afficheurVideoSource, SIGNAL("pictureChanged(int)"), self.getFile) #-------------------------------------------------------------------- # ------------------------------------------------------------------- # Boîte de groupe : "Fichiers source" # ------------------------------------------------------------------- extFormat=[] for fmt in parent.soxSuppFormat : extFormat.append("*."+fmt) self.afficheurAudioSource=SelectWidget(extensions=extFormat ,mode="texte", audio = True) # Onglets self.indexAudioSource = self.add(self.afficheurAudioSource, _(u'Audio(s) source')) self.connect(self.afficheurAudioSource,SIGNAL("fileSelected"), self.getFileA) self.connect(self.afficheurAudioSource, SIGNAL("pictureChanged(int)"), self.getFileA) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = ["video","audio"] # Défini le type de fichier source. self.typeSortie = "video" # Défini le type de fichier de sortie. self.sourceEntrees = [self.afficheurVideoSource, self.afficheurAudioSource] # Fait le lien avec le sélecteur de fichier source. self.addReglage("vbox") #=== Widget qui seront inclus dans la boite de réglage ===# self.ordreVideo = selectJoinMultipleSound(0, self) self.ordreVideo.setTitleAndTips(_(u"Fichiers vidéos à joindre"), _(u"Liste des fichiers vidéo à joindre. <b>Pour information, vous pouvez monter et descendre les fichiers grâce aux flèches haut et bas (les fichiers apparaissant en haut de la liste sont ceux qui seront au début du montage)</b>")) self.ordreAudio = selectJoinMultipleSound(0, self) self.ordreAudio.setTitleAndTips(_(u"Fichiers audios à joindre"), _(u"Liste des fichiers audio à joindre. <b>Pour information, vous pouvez monter et descendre les fichiers grâce aux flèches haut et bas (les fichiers apparaissant en haut de la liste sont ceux qui seront au début du montage)</b>")) self.layoutReglage.addWidget(self.ordreVideo) self.layoutReglage.addWidget(self.ordreAudio) # --------------------------- # Boite de groupe de mplayer # --------------------------- self.addPreview() self.addLog()
class Animation_MontagVideoDecoupUneVideo(Base): # ------------------------------------------------------------------------------------- # Cadre accueillant les widgets de : Animation >> Montage Vidéo >> Découper une Vidéo # ------------------------------------------------------------------------------------- def __init__(self, statusBar): # ------------------------------- # Parametres généraux du widget # ------------------------------- # === tout sera mis dans une boîte verticale ===# self.vbox = QVBoxLayout() # === Variable de configuration ===# self.config = EkdConfig # === Identifiant de la classe ===# self.idSection = "animation_decouper_une_video" super(Animation_MontagVideoDecoupUneVideo, self).__init__( "vbox", titre=_(u"Montage: Découpage d'une vidéo") ) # module de animation self.printSection() # === Drapeaux ===# # drapeau pour savoir si la valeur de début de la sélection vidéo été enregistré self.debutEstSelect = False # drapeau pour savoir si la valeur de fin de la sélection vidéo été enregistré self.finEstSelect = False # drapeau d'extraction du son self.extraireSon = True # ------------------------------------------------------------------- # Boîte de groupe : "Fichier vidéo source" # ------------------------------------------------------------------- self.afficheurVideoSource = SelectWidget( extensions=["*.avi", "*.mpg", "*.mpeg", "*.mjpeg", "*.flv", "*.mp4", "*.dv", "*.vob"], mode="texte", video=True, ) # Onglets self.indexVideoSource = self.add(self.afficheurVideoSource, _(u"Video(s) source")) self.connect(self.afficheurVideoSource, SIGNAL("fileSelected"), self.getFile) self.connect(self.afficheurVideoSource, SIGNAL("pictureChanged(int)"), self.getFile) # -------------------------------------------------------------------- ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "video" # Défini le type de fichier source. self.typeSortie = "video" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurVideoSource # Fait le lien avec le sélecteur de fichier source. # -------------------------------------------------------------------------- # Boîte de groupe de réglage: visualisation et de marquage de la sélection # -------------------------------------------------------------------------- # Création de la boite de groupe de réglage (contenant la boite self.layoutReglage) reglageGroup = QGroupBox() self.layoutReglage = QVBoxLayout(reglageGroup) # === Widgets mplayer ===# vboxMplayer = QVBoxLayout() # Le facteur limitant est la largeur -> + simple à coder à cause de la largeur de la barre self.mplayer = Mplayer( taille=(300, 270), facteurLimitant=Mplayer.LARGEUR, choixWidget=(Mplayer.RATIO, Mplayer.PAS_PRECEDENT_SUIVANT, Mplayer.CURSEUR_A_PART), ) self.mplayer.listeVideos = [] self.mplayer.setToolTip( _(u"La lecture de la vidéo est nécessaire pour achever la sélection d'une zone de la vidéo") ) self.mplayer.setEnabled(False) hbox = QHBoxLayout() hbox.addStretch() hbox.addWidget(self.mplayer) hbox.addStretch() vboxMplayer.addLayout(hbox) self.radioSource = QRadioButton(_(u"vidéo(s) source(s)")) self.radioSource.setChecked(True) self.radioSource.setEnabled(False) self.connect(self.radioSource, SIGNAL("clicked(bool)"), self.fctRadioSource) self.radioApercu = QRadioButton(_(u"aperçu")) self.radioApercu.setEnabled(False) self.connect(self.radioApercu, SIGNAL("clicked(bool)"), self.fctRadioApercu) self.radioConvert = QRadioButton(_(u"vidéo convertie")) self.radioConvert.setEnabled(False) self.connect(self.radioConvert, SIGNAL("clicked(bool)"), self.fctRadioConvert) self.layoutReglage.addLayout(vboxMplayer) self.add(reglageGroup, _(u"Réglages")) # Le widget-mplayer récupère des informations tous les 10ème # de seconde au lieu de toutes les secondes -> le marquage du # début et de la fin de la sélection seront plus précis self.mplayer.dureeTimer = 100 self.connect(self.mplayer.bout_LectPause, SIGNAL("clicked()"), self.lectureMPlayer) # === Marquage et affichage des bornes de la sélection ===# # ||| Label de visualisation de la sélection |||# self.marques = (0, 0, 1) self.visuSelect = Label(self.marques, 300) self.visuSelect.setToolTip(_(u"La zone sélectionnée apparait en vert dans cette bande")) self.valeurDebut = 0 self.valeurFin = 0 hbox = QHBoxLayout() hbox.addWidget(self.visuSelect) self.layoutReglage.addLayout(hbox) # ||| boutons de marquage de la sélection |||# iconTaille = 28 self.frameMarque = QFrame() boiteMarque = QHBoxLayout() boiteMarque.addStretch() boutMarqDebutSelect = QPushButton() boutMarqDebutSelect.setIcon(QIcon("Icones" + os.sep + "Tdebut.png")) boutMarqDebutSelect.setIconSize(QSize(iconTaille, iconTaille)) boutMarqDebutSelect.setToolTip(_(u"Marquer le début de la sélection")) boiteMarque.addWidget(boutMarqDebutSelect) self.boutMarqFinSelect = QPushButton() self.boutMarqFinSelect.setIcon(QIcon("Icones" + os.sep + "Tfin.png")) self.boutMarqFinSelect.setIconSize(QSize(iconTaille, iconTaille)) self.boutMarqFinSelect.setToolTip(_(u"Marquer la fin de la sélection")) self.boutMarqFinSelect.setEnabled(False) boiteMarque.addWidget(self.boutMarqFinSelect) boutMarqDebutSelect_min = QPushButton() boutMarqDebutSelect_min.setIcon(QIcon("Icones" + os.sep + "Tdebut2.png")) boutMarqDebutSelect_min.setIconSize(QSize(iconTaille, iconTaille)) boutMarqDebutSelect_min.setToolTip(_(u"Marquer le début de la sélection au temps minimum (t=0)")) boiteMarque.addWidget(boutMarqDebutSelect_min) self.boutMarqFinSelect_max = QPushButton() self.boutMarqFinSelect_max.setIcon(QIcon("Icones" + os.sep + "Tfin2.png")) self.boutMarqFinSelect_max.setIconSize(QSize(iconTaille, iconTaille)) self.boutMarqFinSelect_max.setToolTip( _(u'Marquer la fin de la sélection au temps maximum (t="la durée de la vidéo")') ) self.boutMarqFinSelect_max.setEnabled(False) boiteMarque.addWidget(self.boutMarqFinSelect_max) boutMiseAZeroSelect = QPushButton() boutMiseAZeroSelect.setIcon(QIcon("Icones" + os.sep + "update.png")) boutMiseAZeroSelect.setIconSize(QSize(iconTaille, iconTaille)) boutMiseAZeroSelect.setToolTip(_(u"Remettre à zéro les paramètres")) boiteMarque.addWidget(boutMiseAZeroSelect) self.boutExtractionSon = QPushButton() self.boutExtractionSon.setIcon(QIcon("Icones" + os.sep + "sound.png")) self.boutExtractionSon.setIconSize(QSize(iconTaille, iconTaille)) self.boutExtractionSon.setToolTip(_(u"Pressez le bouton si vous voulez exclure le son de l'extraction vidéo")) boiteMarque.addWidget(self.boutExtractionSon) # ------------------------------------------------------------------------- ## Bouton d'augmentation/réduction de la vitesse taille = QSize(15, 15) speedBox = QVBoxLayout() self.moinsvite = QPushButton("-") self.moinsvite.setFixedSize(taille) self.moinsvite.setToolTip(_(u"Pressez le bouton si vous réduire la vitesse de la vidéo")) speedBox.addWidget(self.moinsvite) self.initspeed = QPushButton("=") self.initspeed.setFixedSize(taille) self.initspeed.setToolTip(_(u"Pressez le bouton si vous réinitialiser la vitesse de la vidéo")) speedBox.addWidget(self.initspeed) self.plusvite = QPushButton("+") self.plusvite.setFixedSize(taille) self.plusvite.setToolTip(_(u"Pressez le bouton si vous augmenter la vitesse de la vidéo")) speedBox.addWidget(self.plusvite) boiteMarque.addLayout(speedBox) self.connect(self.moinsvite, SIGNAL("clicked()"), self.mplayer.speeddown) self.connect(self.initspeed, SIGNAL("clicked()"), self.mplayer.initspeed) self.connect(self.plusvite, SIGNAL("clicked()"), self.mplayer.speedup) # ------------------------------------------------------------------------- self.connect(boutMarqDebutSelect, SIGNAL("clicked()"), self.marqDebutSelect) self.connect(self.boutMarqFinSelect, SIGNAL("clicked()"), self.marqFinSelect) self.connect(boutMarqDebutSelect_min, SIGNAL("clicked()"), self.marqDebutSelect_min) self.connect(self.boutMarqFinSelect_max, SIGNAL("clicked()"), self.marqFinSelect_max) self.connect(boutMiseAZeroSelect, SIGNAL("clicked()"), self.miseAZeroSelect) self.connect(self.boutExtractionSon, SIGNAL("clicked()"), self.reglageSon) self.frameMarque.setLayout(boiteMarque) # On grise le widget au début self.frameMarque.setEnabled(False) self.layoutReglage.addWidget(self.frameMarque) boiteMarque.addStretch() # ||| boutons radio de lecture de la source ou du fichier converti|||# hbox = QHBoxLayout() hbox.addWidget(self.radioSource) hbox.addWidget(self.radioApercu) hbox.addWidget(self.radioConvert) hbox.setAlignment(Qt.AlignHCenter) self.layoutReglage.addLayout(hbox) self.layoutReglage.addStretch(50) self.addLog() def getFile(self): """ # On utilise la nouvelle interface de récupération des vidéos Récupération de la vidéo source selectionnée """ self.chemin = self.afficheurVideoSource.getFile() self.boutApp.setEnabled(True) self.mplayer.setEnabled(True) self.mplayer.setVideos([self.chemin]) # On active les réglage self.frameMarque.setEnabled(True) self.boutMarqFinSelect_max.setEnabled(True) self.boutMarqFinSelect.setEnabled(True) # Restauration de l'état initial de sélection self.radioSource.setEnabled(True) self.radioSource.setChecked(True) self.radioApercu.setEnabled(False) self.radioApercu.setChecked(False) self.radioConvert.setEnabled(False) self.radioConvert.setChecked(False) self.emit(SIGNAL("loaded")) self.miseAZeroSelect() def lectureMPlayer(self): """Dégriser les 2 boutons de sélection de fin si le début a déjà été sélectionné""" if self.debutEstSelect: self.boutMarqFinSelect.setEnabled(True) self.boutMarqFinSelect_max.setEnabled(True) def reglageSon(self): """Extraire ou pas le son de la vidéo lors de l'extraction vidéo""" if self.extraireSon: self.extraireSon = False self.boutExtractionSon.setIcon(QIcon("Icones" + os.sep + "nosound.png")) self.boutExtractionSon.setToolTip( _(u"Pressez le bouton si vous voulez ré-inclure le son de l'extraction vidéo") ) else: self.extraireSon = True self.boutExtractionSon.setIcon(QIcon("Icones" + os.sep + "sound.png")) self.boutExtractionSon.setToolTip( _(u"Pressez le bouton si vous voulez exclure le son de l'extraction vidéo") ) def miseAZeroSelect(self): """On remet à zéro les zones sélectionnées""" self.debutEstSelect = False self.finEstSelect = False self.boutApp.setEnabled(False) self.boutMarqFinSelect.setEnabled(False) self.boutMarqFinSelect_max.setEnabled(False) self.valeurDebut, self.valeurFin = 0, 0 self.marques = (0, 0, 1) self.visuSelect.update_marques(self.marques) self.visuSelect.repaint() def marqDebutSelect(self): """Récupération de la valeur du marqueur du début de la sélection""" # Le début n'a pas été sélectionné ou il a été sélectionné mais pas la fin # -> un seul trait sur le QLabel if not self.debutEstSelect or (self.debutEstSelect and not self.finEstSelect): self.valeurDebut = self.mplayer.temps # print self.valeurDebut EkdPrint(u"%s" % self.valeurDebut) self.debutEstSelect = True # mplayer est en lecture if self.mplayer.estLue: self.boutMarqFinSelect.setEnabled(True) self.boutMarqFinSelect_max.setEnabled(True) # tracer d'un seul trait self.marques = (self.valeurDebut, self.valeurDebut, self.mplayer.dureeVideo) self.visuSelect.update_marques(self.marques) self.visuSelect.repaint() # Le début et la fin ont déjà été sélectionnés elif self.debutEstSelect and self.finEstSelect: self.valeurDebut = self.mplayer.temps # print self.valeurDebut EkdPrint(u"%s" % self.valeurDebut) self.mettreAJourLabel("debut") def marqFinSelect(self): """Récupération de la valeur du marqueur de la fin de la sélection""" self.valeurFin = self.mplayer.temps # print self.valeurFin EkdPrint(u"%s" % self.valeurFin) self.finEstSelect = True self.mettreAJourLabel("fin") def marqDebutSelect_min(self): """La valeur du marqueur du début de la sélection est placé au temps t=0 seconde""" # Le début n'a pas été sélectionné ou il a été sélectionné mais pas la fin # -> un seul trait sur le QLabel if not self.debutEstSelect or (self.debutEstSelect and not self.finEstSelect): self.valeurDebut = 0 # print self.valeurDebut EkdPrint(u"%s" % self.valeurDebut) self.debutEstSelect = True # mplayer est en lecture if self.mplayer.estLue: self.boutMarqFinSelect.setEnabled(True) self.boutMarqFinSelect_max.setEnabled(True) # tracer d'un seul trait self.marques = (self.valeurDebut, self.valeurDebut, self.mplayer.dureeVideo) self.visuSelect.update_marques(self.marques) self.visuSelect.repaint() # Le début et la fin ont déjà été sélectionnés elif self.debutEstSelect and self.finEstSelect: self.valeurDebut = 0 # print self.valeurDebut EkdPrint(u"%s" % self.valeurDebut) self.mettreAJourLabel() def marqFinSelect_max(self): """La valeur du marqueur de la fin de la sélection est placé à la fin de la vidéo""" self.valeurFin = self.mplayer.dureeVideo # print self.valeurFin EkdPrint(u"%s" % self.valeurFin) self.finEstSelect = True self.mettreAJourLabel() def mettreAJourLabel(self, debutOuFin=None): """Redessiner le QLabel""" # si les valeurs de début et de fin sont identiques (possible car notre précision est de un dixième de seconde) alors on remet tout à zéro if self.valeurDebut == self.valeurFin: self.finEstSelect = False self.boutApp.setEnabled(False) # tracer d'un seul trait self.marques = (self.valeurDebut, self.valeurFin, self.mplayer.dureeVideo) self.visuSelect.update_marques(self.marques) self.visuSelect.repaint() self.radioConvert.setEnabled(False) self.radioApercu.setEnabled(False) # Truisme: la valeur du début de la sélection doit être inférieure à la valeur de la fin # sinon les valeurs de début et de fin sont égalisées elif self.valeurDebut > self.valeurFin: self.radioSource.setEnabled(False) self.radioApercu.setEnabled(False) if debutOuFin == "debut": self.valeurFin = self.valeurDebut self.boutApp.setEnabled(False) self.marques = (self.valeurDebut, self.valeurFin, self.mplayer.dureeVideo) self.visuSelect.update_marques(self.marques) self.visuSelect.repaint() if debutOuFin == "fin": self.valeurDebut = self.valeurFin self.boutApp.setEnabled(False) self.marques = (self.valeurDebut, self.valeurFin, self.mplayer.dureeVideo) self.visuSelect.update_marques(self.marques) self.visuSelect.repaint() # Le cas normal else: self.marques = (self.valeurDebut, self.valeurFin, self.mplayer.dureeVideo) self.visuSelect.update_marques(self.marques) self.visuSelect.repaint() self.boutApp.setEnabled(True) self.radioSource.setEnabled(True) self.radioApercu.setEnabled(True) # print "Bornes de la sélection :", self.valeurDebut, self.valeurFin, type(self.valeurDebut), type(self.valeurFin) EkdPrint( u"Bornes de la sélection : %s %s %s" % (self.valeurDebut, self.valeurFin, type(self.valeurDebut)), type(self.valeurFin), ) def fctRadioSource(self): """"Communique le fichier source à mplayer""" self.mplayer.listeVideos = [self.chemin] self.mplayer.debutFin = (0, 0) self.frameMarque.setEnabled(True) self.visuSelect.setEnabled(True) self.radioApercu.setChecked(False) self.radioConvert.setChecked(False) def fctRadioApercu(self): """"Communique le fichier aperçu à mplayer""" self.mplayer.listeVideos = [self.chemin] debut = float("%.1f" % self.valeurDebut) fin = float("%.1f" % self.valeurFin) self.mplayer.debutFin = (debut, fin) self.frameMarque.setEnabled(False) self.radioSource.setChecked(False) self.radioConvert.setChecked(False) def fctRadioConvert(self): """"Communique le fichier converti à mplayer""" self.mplayer.listeVideos = [self.fichierSortie] self.mplayer.debutFin = (0, 0) self.frameMarque.setEnabled(False) self.visuSelect.setEnabled(False) self.radioSource.setChecked(False) self.radioApercu.setChecked(False) def ouvrirSource(self, nomEntree=None): """Récupération du chemin de la vidéo sélectionnée et activation de certains widgets""" # Récupération du chemin de la vidéo chemin = self.recupSource(nomEntree) if not chemin: return # Affichage du chemin + nom de fichier dans la ligne d'édition self.ligneEditionSource.setText(chemin) self.mplayer.setEnabled(True) self.mplayer.listeVideos = [chemin] self.radioSource.setChecked(True) self.radioSource.setEnabled(False) self.radioConvert.setEnabled(False) # les boutons de marquage apparaissent self.frameMarque.setEnabled(True) # Utile lors de la sélection d'une 2ème vidéo et au-delà self.miseAZeroSelect() def afficherAide(self): """ Boîte de dialogue de l'aide du cadre Animation > Encodage """ super(Animation_MontagVideoDecoupUneVideo, self).afficherAide( _( u"""<p><b>Vous pouvez ici découper une vidéo et ainsi en garder uniquement la partie qui vous intéresse.</b></p><p><font color='green'>Ce cadre est assez différent des autres, vous avez tout d'abord la zone de visualisation vidéo, puis en dessous les boutons marche, arrêt et le compteur, la glissière de défilement, la zone d'affichage de la découpe (les parties découpées seront affichées en vert), les boutons début/fin de sélection, remise à zéro (vous pouvez sélectionner ou non le son par le bouton haut parleur), à la doite de ce dernier bouton, vous avez trois minuscules boutons contenant +, = et - (ils servent à accélérer ou diminuer la vitesse de lecture de la vidéo), puis les choix de visualisation <b>'vidéo(s) source(s)'</b>, <b>'vidéo convertie'</b> et le bouton <b>'Comparateur de vidéos'</b>.</font></p><p><b>Tout ce qui vient de vous être décrit se trouve dans l'onglet Réglages</b>.</p><p>Dans l'onglet <b>'Vidéo(s) source'</b> cliquez sur le bouton <b>Ajouter</b>, une boîte de dialogue apparaît, sur la partie gauche sélectionnez le répertoire (au besoin dépliez les sous-répertoires), allez chercher la/les vidéo(s). Si vous voulez sélectionner plusieurs vidéos d'un coup, maintenez la touche <b>CTRL</b> (ou <b>SHIFT</b>) du clavier enfoncée (tout en sélectionnant vos vidéos), cliquez sur <b>Ajouter</b>.</p><p>Vous pouvez dès lors sélectionner une vidéo dans la liste et la visionner (par le bouton juste à la droite de cette liste), vous noterez que vous pouvez visionner la vidéo en quatre tiers, en seize neuvième ou avec les proportions d'origine de la vidéo (w;h). De même si vous le désirez, vous pouvez obtenir des informations complètes sur la vidéo sélectionnée, et ce par le bouton <b>'Infos'</b> (en bas).</p><p>Dans l'onglet <b>'Réglages'</b>, lisez la vidéo (par le bouton avec la flèche orientée vers la droite <b>'La lecture de la vidéo est nécessaire ...'</b>), pour la vitesse de lecture, profitez des boutons + ou - pour augmenter ou diminuer la vitesse de lecture <b>(plus vous diminuez la vitesse de lecture, plus la découpe pourra se faire de façon précise)</b>, cliquez ensuite sur le bouton <b>'Marquer le début de la sélection'</b> ou <b>'Marquer le début de la sélection au temps minimum (t=0)'</b> (pour sélectionner la vidéo à son tout début), laissez jouer (regardez la vidéo défiler) et cliquez sur le bouton <b>'Marquer la fin de la sélection'</b> au moment propice (ou <b>'Marquer la fin de la sélection au temps maximum t="la durée de la vidéo"'</b> pour garder la dite vidéo jusqu'à la fin).</p><p><font color='blue'>Sachez que vous pouvez revenir aux paramètres par défaut en cliquant sur le bouton <b>'Remettre à zéro les paramètres'</b> (les deux flèches vertes inversées), vous devrez alors rejouer la vidéo et recommencer vos différentes sélections.</font></p><p>Cliquez sur le bouton <b>'Appliquer'</b>, sélectionnez le répertoire de sauvegarde de votre vidéo, entrez le <b>'Nom de Fichier'</b> dans le champ de texte réservé à cet effet ... cliquez sur le bouton <b>'Enregistrer'</b> et attendez le temps de la conversion. A la fin cliquez sur le bouton <b>'Voir les informations d'encodage'</b> et fermez cette dernière fenêtre après avoir vu les informations en question.</p><p>Vous pouvez visionner votre vidéo (avant la conversion) en sélectionnant <b>'vidéo(s) source(s)'</b>, après la conversion <b>'vidéo convertie'</b> ou bien encore les deux en même temps, en cliquant sur le bouton <b>'Comparateur de vidéos'</b>.</p><p>L'onglet <b>'Infos'</b> vous permet de voir les vidéos et fichiers audio chargés (avec leurs chemins exacts) avant et après conversion.</p>""" ) ) def appliquer(self): """Découpage de la vidéo""" # Récupération du chemin source chemin = unicode(self.chemin) # suffix du fichier actif suffix = os.path.splitext(chemin)[1] # Modifié le 30/06/2009 : On joue avec l'éritage de Base saveDialog = EkdSaveDialog(self, mode="video", suffix=suffix, title=_(u"Sauver")) cheminFichierEnregistrerVideo = saveDialog.getFile() if not cheminFichierEnregistrerVideo: return ########################################################################################################################### tempsDebut = float("%.1f" % self.valeurDebut) tempsFin = float("%.1f" % self.valeurFin) dureeSelection = str(tempsFin - tempsDebut) # # Extension du fichier # print "extension :", suffix, type(suffix) EkdPrint(u"extension : %s %s" % (suffix, type(suffix))) try: mencoder = WidgetMEncoder( "decoupervideo", chemin, cheminFichierEnregistrerVideo, valeurNum=(str(tempsDebut), str(dureeSelection)), optionSpeciale=self.extraireSon, laisserOuvert=1, ) mencoder.setWindowTitle(_(u"Découper une vidéo")) mencoder.exec_() except: messageErrAnEnc = QMessageBox(self) messageErrAnEnc.setText(_(u'Un problème est survenu lors de l\'exécution de "mencoder -ss ..."')) messageErrAnEnc.setWindowTitle(_(u"Error")) messageErrAnEnc.setIcon(QMessageBox.Warning) messageErrAnEnc.exec_() return self.fichierSortie = cheminFichierEnregistrerVideo self.radioSource.setEnabled(True) self.radioSource.setChecked(False) self.radioConvert.setEnabled(True) self.radioConvert.setChecked(True) ### Information à l'utilisateur self.infoLog(None, chemin, None, cheminFichierEnregistrerVideo) def saveFiles(self): """ # On sauvegarde la liste des fichiers chargés """ self.afficheurVideoSource.saveFileLocation(self.idSection) # Ajout de la sauvegarde des positions début et fin EkdConfig.set(self.idSection, u"valeurDebut", unicode(self.valeurDebut)) EkdConfig.set(self.idSection, u"valeurFin", unicode(self.valeurFin)) def loadFiles(self): """ # On sauvegarde la liste des fichiers chargés """ self.afficheurVideoSource.loadFileLocation(self.idSection) self.valeurDebut = float(EkdConfig.get(self.idSection, "valeurDebut")) self.valeurFin = float(EkdConfig.get(self.idSection, "valeurFin")) self.mettreAJourLabel("debut") self.mettreAJourLabel("fin") def load(self): """ Chargement de la configuration de tous les objets """ self.loadFiles() def save(self): """ Sauvegarde de la configuration de tous les objets """ self.saveFiles()
def __init__(self): #=== Variable de configuration ===# self.config=EkdConfig # Identifiant de la classe self.idSection = "animation_encodage_avchd" # idSection à modifier lorsqu'il y aura des données dans la config. super(AnimationEncodageAVCHD, self).__init__('hbox', titre=_(u"Transcodage: Gestion de l'AVCHD")) self.printSection() # Customisation de l'interface aux besoins de l'AVCHD # 1. Onglet sources self.afficheurVideoSource=SelectWidget(extensions = ["*.m2ts", "*.mts", "*.m2t", "*.mp4"], mode="texte", video = True) # Onglets self.indexVideoSource = self.add(self.afficheurVideoSource, _(u'Video(s) source')) self.connect(self.afficheurVideoSource,SIGNAL("fileSelected"), self.getFile) self.connect(self.afficheurVideoSource, SIGNAL("pictureChanged(int)"), self.getFile) ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "video" # Défini le type de fichier source. self.typeSortie = "video" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurVideoSource # Fait le lien avec le sélecteur de fichier source. # 2. Onglet réglages groupReglage = QGroupBox() self.layoutReglage = QVBoxLayout(groupReglage) ##=== Widget qui seront inclus dans la boite de réglage ===# layoutSortie = QHBoxLayout() sortie = QLabel(_(u"Encodage sortie vidéo :")) layoutSortie.addWidget(sortie) # self.codecSortie = QComboBox() liste_codecs = ["MOV (.mov)", "VOB (.vob)", "MPEG2 (.mpg)", "MPEG1 (.mpg)", "MPEG4 (.mp4)", "WMV2 (.wmv)", "HFYU (yuv422p) (.avi)", "MSMPEG 4 version 2 (.avi)", "Motion JPEG (.avi)", "FFV1 (FFmpeg) (.avi)", "Avid DNxHD (.mov)"] codecs = QStringList([QString(f) for f in liste_codecs]) # self.codecSortie.addItems(codecs) layoutSortie.addWidget(self.codecSortie) self.layoutReglage.addLayout(layoutSortie) self.connect(self.codecSortie, SIGNAL("currentIndexChanged(int)"), self.changerReglagesCodec) self.connect(self.codecSortie, SIGNAL("activated(QString)"), self.selection) # Widgets pour la sélection de la résolution en sortie --------------------- layoutReso = QHBoxLayout() self.resolu = QLabel(_(u"Résolution sortie :")) layoutReso.addWidget(self.resolu) self.resoSortie = QComboBox() liste_reso = ["1920x1080", "1440x1080", "1280x720", "720x576"] reso = QStringList([QString(r) for r in liste_reso]) self.resoSortie.addItems(reso) layoutReso.addWidget(self.resoSortie) self.layoutReglage.addLayout(layoutReso) self.connect(self.resoSortie, SIGNAL("currentIndexChanged(int)"), self.changerReglagesResol) ### Ajouté le 13/08/2010 ################################################### # Widgets pour la sélection des spécificités DNxHD ------------------------- layoutSpec_DNxHD = QHBoxLayout() self.label_spec_DNxHD = QLabel(_(u"Spécificités :")) layoutSpec_DNxHD.addWidget(self.label_spec_DNxHD) self.specSortie_DNxHD = QComboBox() liste_spec_DNxHD = [_(u"Dimension:1920x1080 Img/sec:29.97 Bitrate:220 Mb/s"), _(u"Dimension:1920x1080 Img/sec:29.97 Bitrate:145 Mb/s"), _(u"Dimension:1920x1080 Img/sec:25 Bitrate:185 Mb/s"), _(u"Dimension:1920x1080 Img/sec:25 Bitrate:120 Mb/s"), _(u"Dimension:1920x1080 Img/sec:25 Bitrate:36 Mb/s"), _(u"Dimension:1920x1080 Img/sec:24 Bitrate:175 Mb/s"), _(u"Dimension:1920x1080 Img/sec:24 Bitrate:115 Mb/s"), _(u"Dimension:1920x1080 Img/sec:24 Bitrate:36 Mb/s"), _(u"Dimension:1920x1080 Img/sec:23.976 Bitrate:175 Mb/s"), _(u"Dimension:1920x1080 Img/sec:23.976 Bitrate:115 Mb/s"), _(u"Dimension:1920x1080 Img/sec:23.976 Bitrate:36 Mb/s"), _(u"Dimension:1920x1080 Img/sec:29.97 Bitrate:220 Mb/s"), _(u"Dimension:1920x1080 Img/sec:29.97 Bitrate:145 Mb/s"), _(u"Dimension:1920x1080 Img/sec:29.97 Bitrate:45 Mb/s"), _(u"Dimension:1280x720 Img/sec:59.94 Bitrate:220 Mb/s"), _(u"Dimension:1280x720 Img/sec:59.94 Bitrate:145 Mb/s"), _(u"Dimension:1280x720 Img/sec:50 Bitrate:175 Mb/s"), _(u"Dimension:1280x720 Img/sec:50 Bitrate:115 Mb/s"), _(u"Dimension:1280x720 Img/sec:29.97 Bitrate:110 Mb/s"), _(u"Dimension:1280x720 Img/sec:29.97 Bitrate:75 Mb/s"), _(u"Dimension:1280x720 Img/sec:25 Bitrate:90 Mb/s"), _(u"Dimension:1280x720 Img/sec:25 Bitrate:60 Mb/s"), _(u"Dimension:1280x720 Img/sec:23.976 Bitrate:90 Mb/s"), _(u"Dimension:1280x720 Img/sec:23.976 Bitrate:60 Mb/s")] spec_DNxHD = QStringList([QString(spec_DNxHD) for spec_DNxHD in liste_spec_DNxHD]) self.specSortie_DNxHD.addItems(spec_DNxHD) layoutSpec_DNxHD.addWidget(self.specSortie_DNxHD) self.layoutReglage.addLayout(layoutSpec_DNxHD) self.connect(self.specSortie_DNxHD, SIGNAL("currentIndexChanged(int)"), self.changerReglagesSpec_DNxHD) # Par défaut ces 2 widgets sont invisibles (comme ça ils n'apparaissent pas # dans le cas d'une sélection autre que Avid DNxHD (.mov)) self.label_spec_DNxHD.hide() self.specSortie_DNxHD.hide() # Widgets pour la sélection du flux audio en sortie uniquement DNxHD ------- layoutSon_DNxHD = QHBoxLayout() self.label_son_DNxHD = QLabel(_(u"Sortie flux audio :")) layoutSon_DNxHD.addWidget(self.label_son_DNxHD) self.sonSortie_DNxHD = QComboBox() liste_son_DNxHD = [_(u"Copie du flux audio"), _(u"Flux audio PCM sur 2 canaux (stereo)"), _(u"Pas de flux audio")] son_DNxHD = QStringList([QString(son_DNxHD) for son_DNxHD in liste_son_DNxHD]) self.sonSortie_DNxHD.addItems(son_DNxHD) layoutSon_DNxHD.addWidget(self.sonSortie_DNxHD) self.layoutReglage.addLayout(layoutSon_DNxHD) self.connect(self.sonSortie_DNxHD, SIGNAL("currentIndexChanged(int)"), self.changerReglagesSon_DNxHD) # Par défaut ces 2 widgets sont invisibles (comme ça ils n'apparaissent pas # dans le cas d'une sélection autre que Avid DNxHD (.mov)) self.label_son_DNxHD.hide() self.sonSortie_DNxHD.hide() ############################################################################ # Widgets pour la sélection du nombre d'images/seconde en sortie ----------- layoutNbrIm = QHBoxLayout() self.labelNbrImage = QLabel(_(u"Nombre d'images/seconde (entre 2 et 60) :")) layoutNbrIm.addWidget(self.labelNbrImage) self.nbrImage = QSpinBox() self.nbrImage.setRange(2, 60) self.nbrImage.setValue(25) layoutNbrIm.addWidget(self.nbrImage) self.layoutReglage.addLayout(layoutNbrIm) self.connect(self.nbrImage, SIGNAL("valueChanged(int)"), self.changerReglagesNbrImgSec) # Widgets pour la sélection de la qualité de la vidéo en sortie ------------ layoutQualite = QHBoxLayout() self.labelQualite = QLabel(_(u"Qualité de la vidéo (2:bonne, 31:mauvaise) :")) layoutQualite.addWidget(self.labelQualite) self.qualite = QSpinBox() self.qualite.setRange(2, 31) self.qualite.setValue(2) layoutQualite.addWidget(self.qualite) self.layoutReglage.addLayout(layoutQualite) self.connect(self.qualite, SIGNAL("valueChanged(int)"), self.changerReglagesQual) ## On charge les options depuis EkdConfig self.loadOptions() ## self.add(groupReglage, _(u"Réglages")) # 3 et 4 Onglet prévisualisation et info. self.addPreview() self.addLog()
def __init__(self, statusBar): # ------------------------------- # Parametres généraux du widget # ------------------------------- # === tout sera mis dans une boîte verticale ===# self.vbox = QVBoxLayout() # === Variable de configuration ===# self.config = EkdConfig # === Identifiant de la classe ===# self.idSection = "animation_decouper_une_video" super(Animation_MontagVideoDecoupUneVideo, self).__init__( "vbox", titre=_(u"Montage: Découpage d'une vidéo") ) # module de animation self.printSection() # === Drapeaux ===# # drapeau pour savoir si la valeur de début de la sélection vidéo été enregistré self.debutEstSelect = False # drapeau pour savoir si la valeur de fin de la sélection vidéo été enregistré self.finEstSelect = False # drapeau d'extraction du son self.extraireSon = True # ------------------------------------------------------------------- # Boîte de groupe : "Fichier vidéo source" # ------------------------------------------------------------------- self.afficheurVideoSource = SelectWidget( extensions=["*.avi", "*.mpg", "*.mpeg", "*.mjpeg", "*.flv", "*.mp4", "*.dv", "*.vob"], mode="texte", video=True, ) # Onglets self.indexVideoSource = self.add(self.afficheurVideoSource, _(u"Video(s) source")) self.connect(self.afficheurVideoSource, SIGNAL("fileSelected"), self.getFile) self.connect(self.afficheurVideoSource, SIGNAL("pictureChanged(int)"), self.getFile) # -------------------------------------------------------------------- ## --------------------------------------------------------------------- # Variables pour la fonction tampon ## --------------------------------------------------------------------- self.typeEntree = "video" # Défini le type de fichier source. self.typeSortie = "video" # Défini le type de fichier de sortie. self.sourceEntrees = self.afficheurVideoSource # Fait le lien avec le sélecteur de fichier source. # -------------------------------------------------------------------------- # Boîte de groupe de réglage: visualisation et de marquage de la sélection # -------------------------------------------------------------------------- # Création de la boite de groupe de réglage (contenant la boite self.layoutReglage) reglageGroup = QGroupBox() self.layoutReglage = QVBoxLayout(reglageGroup) # === Widgets mplayer ===# vboxMplayer = QVBoxLayout() # Le facteur limitant est la largeur -> + simple à coder à cause de la largeur de la barre self.mplayer = Mplayer( taille=(300, 270), facteurLimitant=Mplayer.LARGEUR, choixWidget=(Mplayer.RATIO, Mplayer.PAS_PRECEDENT_SUIVANT, Mplayer.CURSEUR_A_PART), ) self.mplayer.listeVideos = [] self.mplayer.setToolTip( _(u"La lecture de la vidéo est nécessaire pour achever la sélection d'une zone de la vidéo") ) self.mplayer.setEnabled(False) hbox = QHBoxLayout() hbox.addStretch() hbox.addWidget(self.mplayer) hbox.addStretch() vboxMplayer.addLayout(hbox) self.radioSource = QRadioButton(_(u"vidéo(s) source(s)")) self.radioSource.setChecked(True) self.radioSource.setEnabled(False) self.connect(self.radioSource, SIGNAL("clicked(bool)"), self.fctRadioSource) self.radioApercu = QRadioButton(_(u"aperçu")) self.radioApercu.setEnabled(False) self.connect(self.radioApercu, SIGNAL("clicked(bool)"), self.fctRadioApercu) self.radioConvert = QRadioButton(_(u"vidéo convertie")) self.radioConvert.setEnabled(False) self.connect(self.radioConvert, SIGNAL("clicked(bool)"), self.fctRadioConvert) self.layoutReglage.addLayout(vboxMplayer) self.add(reglageGroup, _(u"Réglages")) # Le widget-mplayer récupère des informations tous les 10ème # de seconde au lieu de toutes les secondes -> le marquage du # début et de la fin de la sélection seront plus précis self.mplayer.dureeTimer = 100 self.connect(self.mplayer.bout_LectPause, SIGNAL("clicked()"), self.lectureMPlayer) # === Marquage et affichage des bornes de la sélection ===# # ||| Label de visualisation de la sélection |||# self.marques = (0, 0, 1) self.visuSelect = Label(self.marques, 300) self.visuSelect.setToolTip(_(u"La zone sélectionnée apparait en vert dans cette bande")) self.valeurDebut = 0 self.valeurFin = 0 hbox = QHBoxLayout() hbox.addWidget(self.visuSelect) self.layoutReglage.addLayout(hbox) # ||| boutons de marquage de la sélection |||# iconTaille = 28 self.frameMarque = QFrame() boiteMarque = QHBoxLayout() boiteMarque.addStretch() boutMarqDebutSelect = QPushButton() boutMarqDebutSelect.setIcon(QIcon("Icones" + os.sep + "Tdebut.png")) boutMarqDebutSelect.setIconSize(QSize(iconTaille, iconTaille)) boutMarqDebutSelect.setToolTip(_(u"Marquer le début de la sélection")) boiteMarque.addWidget(boutMarqDebutSelect) self.boutMarqFinSelect = QPushButton() self.boutMarqFinSelect.setIcon(QIcon("Icones" + os.sep + "Tfin.png")) self.boutMarqFinSelect.setIconSize(QSize(iconTaille, iconTaille)) self.boutMarqFinSelect.setToolTip(_(u"Marquer la fin de la sélection")) self.boutMarqFinSelect.setEnabled(False) boiteMarque.addWidget(self.boutMarqFinSelect) boutMarqDebutSelect_min = QPushButton() boutMarqDebutSelect_min.setIcon(QIcon("Icones" + os.sep + "Tdebut2.png")) boutMarqDebutSelect_min.setIconSize(QSize(iconTaille, iconTaille)) boutMarqDebutSelect_min.setToolTip(_(u"Marquer le début de la sélection au temps minimum (t=0)")) boiteMarque.addWidget(boutMarqDebutSelect_min) self.boutMarqFinSelect_max = QPushButton() self.boutMarqFinSelect_max.setIcon(QIcon("Icones" + os.sep + "Tfin2.png")) self.boutMarqFinSelect_max.setIconSize(QSize(iconTaille, iconTaille)) self.boutMarqFinSelect_max.setToolTip( _(u'Marquer la fin de la sélection au temps maximum (t="la durée de la vidéo")') ) self.boutMarqFinSelect_max.setEnabled(False) boiteMarque.addWidget(self.boutMarqFinSelect_max) boutMiseAZeroSelect = QPushButton() boutMiseAZeroSelect.setIcon(QIcon("Icones" + os.sep + "update.png")) boutMiseAZeroSelect.setIconSize(QSize(iconTaille, iconTaille)) boutMiseAZeroSelect.setToolTip(_(u"Remettre à zéro les paramètres")) boiteMarque.addWidget(boutMiseAZeroSelect) self.boutExtractionSon = QPushButton() self.boutExtractionSon.setIcon(QIcon("Icones" + os.sep + "sound.png")) self.boutExtractionSon.setIconSize(QSize(iconTaille, iconTaille)) self.boutExtractionSon.setToolTip(_(u"Pressez le bouton si vous voulez exclure le son de l'extraction vidéo")) boiteMarque.addWidget(self.boutExtractionSon) # ------------------------------------------------------------------------- ## Bouton d'augmentation/réduction de la vitesse taille = QSize(15, 15) speedBox = QVBoxLayout() self.moinsvite = QPushButton("-") self.moinsvite.setFixedSize(taille) self.moinsvite.setToolTip(_(u"Pressez le bouton si vous réduire la vitesse de la vidéo")) speedBox.addWidget(self.moinsvite) self.initspeed = QPushButton("=") self.initspeed.setFixedSize(taille) self.initspeed.setToolTip(_(u"Pressez le bouton si vous réinitialiser la vitesse de la vidéo")) speedBox.addWidget(self.initspeed) self.plusvite = QPushButton("+") self.plusvite.setFixedSize(taille) self.plusvite.setToolTip(_(u"Pressez le bouton si vous augmenter la vitesse de la vidéo")) speedBox.addWidget(self.plusvite) boiteMarque.addLayout(speedBox) self.connect(self.moinsvite, SIGNAL("clicked()"), self.mplayer.speeddown) self.connect(self.initspeed, SIGNAL("clicked()"), self.mplayer.initspeed) self.connect(self.plusvite, SIGNAL("clicked()"), self.mplayer.speedup) # ------------------------------------------------------------------------- self.connect(boutMarqDebutSelect, SIGNAL("clicked()"), self.marqDebutSelect) self.connect(self.boutMarqFinSelect, SIGNAL("clicked()"), self.marqFinSelect) self.connect(boutMarqDebutSelect_min, SIGNAL("clicked()"), self.marqDebutSelect_min) self.connect(self.boutMarqFinSelect_max, SIGNAL("clicked()"), self.marqFinSelect_max) self.connect(boutMiseAZeroSelect, SIGNAL("clicked()"), self.miseAZeroSelect) self.connect(self.boutExtractionSon, SIGNAL("clicked()"), self.reglageSon) self.frameMarque.setLayout(boiteMarque) # On grise le widget au début self.frameMarque.setEnabled(False) self.layoutReglage.addWidget(self.frameMarque) boiteMarque.addStretch() # ||| boutons radio de lecture de la source ou du fichier converti|||# hbox = QHBoxLayout() hbox.addWidget(self.radioSource) hbox.addWidget(self.radioApercu) hbox.addWidget(self.radioConvert) hbox.setAlignment(Qt.AlignHCenter) self.layoutReglage.addLayout(hbox) self.layoutReglage.addStretch(50) self.addLog()