diff --git a/.gitignore b/.gitignore index 954794d..11baa7f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,11 @@ +# Files +*.exe +*.spec +*.bak +*.avi + +# Folders __pycache__ -*.bak \ No newline at end of file +build +dist +.idea \ No newline at end of file diff --git a/PyVideo.ico b/PyVideo.ico new file mode 100644 index 0000000..e0ae9c7 Binary files /dev/null and b/PyVideo.ico differ diff --git a/__main__.py b/__main__.py index 7e69128..2c01666 100644 --- a/__main__.py +++ b/__main__.py @@ -19,3 +19,4 @@ def OnInit(self): if __name__ == "__main__": app = MainApp(0) app.MainLoop() + pass \ No newline at end of file diff --git a/dialog_effects.py b/dialog_effects.py index 475bc67..e72fc2d 100644 --- a/dialog_effects.py +++ b/dialog_effects.py @@ -1,19 +1,29 @@ -import wx +import wx +ID_OK = 20000 +ID_BACK = 20001 + + +##################################################################### +# wx.Dialog Effects +##################################################################### class Effects(wx.Dialog): def __init__(self, *args, **kwds): kwds["style"] = kwds.get("style", 0) | wx.CAPTION wx.Dialog.__init__(self, *args, **kwds) - self.list_box_1 = wx.ListBox(self, wx.ID_ANY, choices=[r"Blur", r"GaussianBlur", r"medianBlur", r"bilateralFilter"]) + self.list_box_1 = wx.ListBox(self, wx.ID_ANY, choices=[r"--", r"Blur", r"GaussianBlur", r"medianBlur", r"bilateralFilter"]) self.spin_1 = wx.SpinCtrl(self, wx.ID_ANY, "0", min=0, max=100) self.spin_2 = wx.SpinCtrl(self, wx.ID_ANY, "0", min=0, max=100) self.spin_3 = wx.SpinCtrl(self, wx.ID_ANY, "0", min=0, max=100) - self.button_ok = wx.Button(self, wx.ID_ANY, r"Применить") - self.button_back = wx.Button(self, wx.ID_ANY, r"Назад") + self.button_ok = wx.Button(self, ID_OK, r"Применить") + self.button_back = wx.Button(self, ID_BACK, r"Назад") self.__set_properties() self.__do_layout() + self.__update_ui() + self.Bind(wx.EVT_LISTBOX, self.onListbox, id=wx.ID_ANY) + self.Bind(wx.EVT_BUTTON, self.onButton, id=wx.ID_ANY) def __set_properties(self): self.SetTitle(r"Добавить эффект") @@ -28,19 +38,19 @@ def __do_layout(self): sizer_1 = wx.BoxSizer(wx.HORIZONTAL) sizer_1.Add(self.list_box_1, 1, 0, 0) sizer_0.Add(sizer_1, 0, wx.EXPAND, 0) - label_1 = wx.StaticText(self, wx.ID_ANY, r"Параметр 1") - label_1.SetMinSize((80, 16)) - sizer_2.Add(label_1, 0, wx.ALIGN_CENTER_VERTICAL, 0) + self.label_1 = wx.StaticText(self, wx.ID_ANY, r"Параметр 1") + self.label_1.SetMinSize((80, 16)) + sizer_2.Add(self.label_1, 0, wx.ALIGN_CENTER_VERTICAL, 0) sizer_2.Add(self.spin_1, 0, 0, 0) sizer_0.Add(sizer_2, 0, wx.EXPAND, 0) - label_2 = wx.StaticText(self, wx.ID_ANY, r"Параметр 2") - label_2.SetMinSize((80, 16)) - sizer_3.Add(label_2, 0, wx.ALIGN_CENTER_VERTICAL, 0) + self.label_2 = wx.StaticText(self, wx.ID_ANY, r"Параметр 2") + self.label_2.SetMinSize((80, 16)) + sizer_3.Add(self.label_2, 0, wx.ALIGN_CENTER_VERTICAL, 0) sizer_3.Add(self.spin_2, 0, 0, 0) sizer_0.Add(sizer_3, 0, wx.EXPAND, 0) - label_3 = wx.StaticText(self, wx.ID_ANY, r"Параметр 3") - label_3.SetMinSize((80, 16)) - sizer_4.Add(label_3, 0, wx.ALIGN_CENTER_VERTICAL, 0) + self.label_3 = wx.StaticText(self, wx.ID_ANY, r"Параметр 3") + self.label_3.SetMinSize((80, 16)) + sizer_4.Add(self.label_3, 0, wx.ALIGN_CENTER_VERTICAL, 0) sizer_4.Add(self.spin_3, 0, 0, 0) sizer_0.Add(sizer_4, 0, wx.EXPAND, 0) sizer_5.Add(self.button_ok, 1, 0, 0) @@ -50,3 +60,37 @@ def __do_layout(self): sizer_0.Fit(self) self.Layout() + + def __update_ui(self): + sel = self.list_box_1.GetSelection() + par1 = par2 = par3 = "" + par1_en, par2_en, par3_en = False, False, False + if sel == 1: + par1, par2 = "Ширина ядра", "Высота ядра" + par1_en, par2_en = True, True + elif sel == 2: + par1, par2 = "Ширина ядра", "Высота ядра" + par1_en, par2_en, par3_en = True, True + elif sel == 3: + par1 = "Параметр" + par1_en = True + elif sel == 4: + par1, par2, par3 = "Параметр1", "Параметр2", "Параметр3" + par1_en, par2_en, par3_en = True, True, True + self.label_1.SetLabel(par1) + self.label_2.SetLabel(par2) + self.label_3.SetLabel(par3) + self.spin_1.Enable(par1_en) + self.spin_2.Enable(par2_en) + self.spin_3.Enable(par3_en) + + + def onListbox(self, event): + self.__update_ui() + event.Skip() + + def onButton(self, event): + if event.Id == ID_BACK or event.Id == ID_OK: + self.Result = event.Id == ID_OK + self.Close() + event.Skip() diff --git a/engine.py b/engine.py index 4781f6d..81fe4d9 100644 --- a/engine.py +++ b/engine.py @@ -1,6 +1,11 @@ import cv2 as cv +import os +import sys +def resource_path(relative): + return (sys._MEIPASS+"/" if getattr(sys, 'frozen', False) else "")+relative + def to_number(x, y=None): try: x = int(x) @@ -11,20 +16,56 @@ def to_number(x, y=None): return x +def apply_effect(frame, eff: list): + if len(eff) < 1: + return frame + eff_n = eff[0] + if eff_n == 1: + return cv.blur(frame, (eff[1], eff[2])) + elif eff_n == 2: + return cv.GaussianBlur(frame, (eff[1], eff[2])) + elif eff_n == 3: + return cv.medianBlur(frame, eff[1]) + elif eff_n == 4: + return cv.bilateralFilter(frame, eff[1], eff[2], eff[3]) + else: + return frame + + class VideoFeed(object): def __init__(self, filename=None, resize=False): + self.effect = [0] self.open(filename, resize) def open(self, filename=None, resize=False): self.video = cv.VideoCapture(filename if filename else 0) - + print(self.video.isOpened()) self.resize = resize def opened(self): return self.video.isOpened() - def next_frame(self, w = None, h = None): + def saveto(self, name): + fourcc = cv.VideoWriter_fourcc(*'XVID') + out = cv.VideoWriter(name, + fourcc, + self.get_fps(), + (self.get_frame_size()) + ) + pos = self.get_position() + self.set_position(0) + while True: + frame = self.next_frame() + if frame is None: + break + out.write(frame) + self.set_position(pos) + out.release() + + def next_frame(self, w=None, h=None): ret, frame = self.video.read() + if ret and self.effect[0] != 0: + frame = apply_effect(frame, self.effect) if ret and self.resize and w and h: frame = cv.resize(frame, (w, h)) return frame if ret else None @@ -34,7 +75,7 @@ def length(self): def get_frame_size(self): v = self.video - return v.get(cv.CAP_PROP_FRAME_WIDTH), v.get(cv.CAP_PROP_FRAME_HEIGHT) + return int(v.get(cv.CAP_PROP_FRAME_WIDTH)), int(v.get(cv.CAP_PROP_FRAME_HEIGHT)) # def get_position(self): diff --git a/forms/Effects.py b/forms/Effects.py index 19fcf9e..e0747b3 100644 --- a/forms/Effects.py +++ b/forms/Effects.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# generated by wxGlade 0.9.5 on Fri May 15 18:56:35 2020 +# generated by wxGlade 0.9.5 on Fri May 15 19:17:55 2020 # import wx @@ -19,7 +19,7 @@ def __init__(self, *args, **kwds): # begin wxGlade: Effects.__init__ kwds["style"] = kwds.get("style", 0) | wx.CAPTION wx.Dialog.__init__(self, *args, **kwds) - self.list_box_1 = wx.ListBox(self, wx.ID_ANY, choices=[_("Blur"), _("GaussianBlur"), _("medianBlur"), _("bilateralFilter")]) + self.list_box_1 = wx.ListBox(self, wx.ID_ANY, choices=[_("--"), _("Blur"), _("GaussianBlur"), _("medianBlur"), _("bilateralFilter")]) self.spin_1 = wx.SpinCtrl(self, wx.ID_ANY, "0", min=0, max=100) self.spin_2 = wx.SpinCtrl(self, wx.ID_ANY, "0", min=0, max=100) self.spin_3 = wx.SpinCtrl(self, wx.ID_ANY, "0", min=0, max=100) diff --git a/forms/Effects.wxg b/forms/Effects.wxg index 68c676b..09195b6 100644 --- a/forms/Effects.wxg +++ b/forms/Effects.wxg @@ -1,5 +1,5 @@ - + @@ -21,6 +21,7 @@ 0 + -- Blur GaussianBlur medianBlur diff --git a/frame_main.py b/frame_main.py index 40c7236..a868bba 100644 --- a/frame_main.py +++ b/frame_main.py @@ -1,5 +1,5 @@ -import wx -from engine import VideoFeed +import wx +from engine import resource_path, VideoFeed from dialog_effects import Effects ID_OPEN = 10000 @@ -22,7 +22,7 @@ def __init__(self, *args, **kwds): self.changed = False self.feed: VideoFeed = None - self.dialog = Effects(None, wx.ID_ANY, "") + self.effects_dlg = Effects(None, wx.ID_ANY, "") kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE wx.Frame.__init__(self, *args, **kwds) @@ -32,26 +32,26 @@ def __init__(self, *args, **kwds): self.toolbar = wx.ToolBar(self, -1, style=wx.TB_DEFAULT_STYLE | wx.TB_TEXT | wx.TB_HORZ_TEXT) self.SetToolBar(self.toolbar) self.toolbar.AddTool(ID_OPEN, r"Открыть", - wx.Bitmap("icons\\open.png", wx.BITMAP_TYPE_ANY), - wx.Bitmap("icons\\open.disabled.png", wx.BITMAP_TYPE_ANY), + wx.Bitmap(resource_path("icons/open.png"), wx.BITMAP_TYPE_ANY), + wx.Bitmap(resource_path("icons/open.disabled.png"), wx.BITMAP_TYPE_ANY), wx.ITEM_NORMAL, r"Открыть видео...", "") self.toolbar.AddTool(ID_SAVEAS, r"Сохранить как", - wx.Bitmap("icons\\save.png", wx.BITMAP_TYPE_ANY), - wx.Bitmap("icons\\save.disabled.png", wx.BITMAP_TYPE_ANY), + wx.Bitmap(resource_path("icons/save.png"), wx.BITMAP_TYPE_ANY), + wx.Bitmap(resource_path("icons/save.disabled.png"), wx.BITMAP_TYPE_ANY), wx.ITEM_NORMAL, r"Сохранить видео как...", "") self.toolbar.AddSeparator() self.toolbar.AddTool(ID_UNDO, r"Отменить", - wx.Bitmap("icons\\undo.png", wx.BITMAP_TYPE_ANY), - wx.Bitmap("icons\\undo.disabled.png", wx.BITMAP_TYPE_ANY), + wx.Bitmap(resource_path("icons/undo.png"), wx.BITMAP_TYPE_ANY), + wx.Bitmap(resource_path("icons/undo.disabled.png"), wx.BITMAP_TYPE_ANY), wx.ITEM_NORMAL, r"Отменить", "") self.toolbar.AddTool(ID_REDO, r"Повторить", - wx.Bitmap("icons\\redo.png", wx.BITMAP_TYPE_ANY), - wx.Bitmap("icons\\redo.disabled.png", wx.BITMAP_TYPE_ANY), + wx.Bitmap(resource_path("icons/redo.png"), wx.BITMAP_TYPE_ANY), + wx.Bitmap(resource_path("icons/redo.disabled.png"), wx.BITMAP_TYPE_ANY), wx.ITEM_NORMAL, r"Повторить", "") self.toolbar.AddSeparator() self.toolbar.AddTool(ID_NEWEFFECT, r"Новый эффект", - wx.Bitmap("icons\\effect.png", wx.BITMAP_TYPE_ANY), - wx.Bitmap("icons\\effect.disabled.png", wx.BITMAP_TYPE_ANY), + wx.Bitmap(resource_path("icons/effect.png"), wx.BITMAP_TYPE_ANY), + wx.Bitmap(resource_path("icons/effect.disabled.png"), wx.BITMAP_TYPE_ANY), wx.ITEM_NORMAL, r"Новый эффект", "") # @@ -60,10 +60,10 @@ def __init__(self, *args, **kwds): # self.panel = wx.Panel(self, wx.ID_ANY, style=wx.BORDER_STATIC) self.play_button = wx.BitmapButton(self.panel, ID_PLAY, - wx.Bitmap("icons\\play.png", wx.BITMAP_TYPE_ANY)) - self.play_button.SetBitmapDisabled(wx.Bitmap("icons\\play.disabled.png", wx.BITMAP_TYPE_ANY)) + wx.Bitmap(resource_path("icons/play.png"), wx.BITMAP_TYPE_ANY)) + self.play_button.SetBitmapDisabled(wx.Bitmap(resource_path("icons/play.disabled.png"), wx.BITMAP_TYPE_ANY)) self.repeat_button = wx.BitmapButton(self.panel, ID_REPEAT, - wx.Bitmap("icons\\repeat.png", wx.BITMAP_TYPE_ANY)) + wx.Bitmap(resource_path("icons/repeat.png"), wx.BITMAP_TYPE_ANY)) self.slider = wx.Slider(self.panel, ID_SLIDER, 0, 0, 10) # @@ -107,21 +107,22 @@ def __do_layout(self): def __update_ui(self): feed_on = self.feed is not None - changed = self.changed + changed = feed_on and self.feed.effect[0] != 0 self.toolbar.EnableTool(ID_SAVEAS, changed) self.toolbar.EnableTool(ID_UNDO, changed) self.toolbar.EnableTool(ID_REDO, False) self.toolbar.EnableTool(ID_NEWEFFECT, feed_on) - play_bmp = {False: "icons\\play.png", True: "icons\\pause.png"} + play_bmp = {False: resource_path("icons/play.png"), True: resource_path("icons/pause.png")} self.play_button.Enable(feed_on) self.play_button.SetBitmap(wx.Bitmap(play_bmp[self.playing], wx.BITMAP_TYPE_ANY)) self.slider.Enable(feed_on) - repeat_bmp = {False: "icons\\repeat.false.png", True: "icons\\repeat.png"} + repeat_bmp = {False: resource_path("icons/repeat.false.png"), True: resource_path("icons/repeat.png")} self.repeat_button.SetBitmap(wx.Bitmap(repeat_bmp[self.repeat], wx.BITMAP_TYPE_ANY)) # TODO: уточнить как должна правильно отображаться кнопка повтора + def Toggle_Play(self): self.playing = not self.playing if self.playing: @@ -130,11 +131,12 @@ def Toggle_Play(self): self.timer.Stop() self.__update_ui() + def onClose(self, event): self.timer.Stop() + self.effects_dlg.Destroy() self.Destroy() - def onScrool(self, event): pl = self.playing if pl: @@ -145,7 +147,7 @@ def onScrool(self, event): def onUpdate(self, event): self.Refresh() - pass + event.Skip() def onEraseBackground(self, event): return @@ -173,6 +175,7 @@ def onPaint(self, event): self.slider.Value = self.feed.get_position() else: self.SetBackgroundColour('White') # TODO: разобраться с этим костылём + event.Skip() def onToolbarClick(self, event): open_wildcard = "AVI (*.avi)|*.avi|All files (*.*)|*.*" @@ -187,14 +190,23 @@ def onToolbarClick(self, event): dialog = wx.FileDialog(None, message="Сохранить как...", defaultDir="", wildcard=save_wildcard, style=wx.FD_SAVE) if dialog.ShowModal() == wx.ID_OK: - self.save_file(dialog.GetPath()) + self.feed.saveto(dialog.GetPath()) if event.Id == ID_UNDO: pass if event.Id == ID_REDO: pass if event.Id == ID_NEWEFFECT: - self.dialog.Centre() - self.dialog.ShowModal() + self.effects_dlg.Centre() + self.effects_dlg.ShowModal() + if self.effects_dlg.Result: + dlg = self.effects_dlg + self.feed.effect = [ + dlg.list_box_1.GetSelection(), + dlg.spin_1.GetValue(), + dlg.spin_2.GetValue(), + dlg.spin_3.GetValue() + ] + self.__update_ui() event.Skip() def onButtonClick(self, event): @@ -205,6 +217,7 @@ def onButtonClick(self, event): self.__update_ui() event.Skip() + def load_file(self, name): feed = VideoFeed(name, True) if feed.opened(): @@ -213,6 +226,3 @@ def load_file(self, name): self.__update_ui() else: del feed - - def save_file(self, name): - pass \ No newline at end of file diff --git a/makeexe.py b/makeexe.py new file mode 100644 index 0000000..0514f83 --- /dev/null +++ b/makeexe.py @@ -0,0 +1,4 @@ +import os, sys +s = os.path.split(sys.executable)[0] +os.system("pyinstaller --noupx -w --add-data icons;icons --add-binary %s/Lib/site-packages/cv2/opencv_videoio_ffmpeg420.dll;. -y -F -i PyVideo.ico -n PyVideo.exe __main__.py" % s) +os.system("pause") \ No newline at end of file