aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--options.py33
-rw-r--r--output.py139
-rw-r--r--sources.py4
-rw-r--r--vidslice.bat1
-rw-r--r--vidslice.py61
5 files changed, 214 insertions, 24 deletions
diff --git a/options.py b/options.py
index e93826f..763eba6 100644
--- a/options.py
+++ b/options.py
@@ -197,10 +197,10 @@ class OptionsPanel(wx.Panel):
self.height.set_calc_new(round(orig_height / orig_width * new_width))
orig_framerate = float(self.framerate.get_orig())
- self.framerate.set_range(0, 10 * orig_framerate)
+ self.framerate.set_range(0, orig_framerate)
self.framerate.set_calc_new(orig_framerate)
- def update(self, path, info):
+ def update(self, info):
import fractions
if info is None:
@@ -224,3 +224,32 @@ class OptionsPanel(wx.Panel):
self.Enable()
self.Layout()
self.enforce_constraints()
+
+ def ffmpeg_opts(self):
+ opts = []
+
+ if self.start_time.is_edit():
+ opts += ['-ss', str(self.start_time.get_final())]
+ elif self.end_time.is_edit() and self.duration.is_edit():
+ new_end = float(self.end_time.get_final())
+ new_duration = float(self.duration.get_final())
+ new_start = new_end - new_duration
+ opts += ['-ss', str(new_start)]
+ if self.end_time.is_edit():
+ opts += ['-to', str(self.end_time.get_final())]
+ if self.duration.is_edit():
+ opts += ['-t', str(self.duration.get_final())]
+
+ if self.width.is_edit() or self.height.is_edit():
+ width = str(self.width.get_final())
+ height = str(self.height.get_final())
+ if not self.width.is_edit():
+ width = "-1"
+ if not self.height.is_edit():
+ height = "-1"
+ opts += ['-vf', 'scale=' + width + ':' + height]
+
+ if self.framerate.is_edit():
+ opts += ['-r', str(self.framerate.get_final())]
+
+ return opts
diff --git a/output.py b/output.py
new file mode 100644
index 0000000..6f46f01
--- /dev/null
+++ b/output.py
@@ -0,0 +1,139 @@
+import os
+import subprocess
+import threading
+
+import wx
+
+
+class OutputPanel(wx.Panel):
+ def __init__(self, *args, get_ffmpeg_args=lambda: [], **kw):
+ super(OutputPanel, self).__init__(*args, **kw)
+ self.update_listeners = []
+ self.input_path = None
+ self.get_ffmpeg_args = get_ffmpeg_args
+
+ root_sizer = wx.StaticBoxSizer(wx.VERTICAL, self, label="Output")
+ self.SetSizer(root_sizer)
+
+ file_panel = wx.Panel(self)
+ root_sizer.Add(file_panel, flag=wx.EXPAND, border=5)
+ file_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ file_panel.SetSizer(file_sizer)
+ file_sizer.Add(wx.StaticText(file_panel, label="File"), flag=wx.EXPAND, border=5)
+ self.file_text = wx.TextCtrl(file_panel)
+ self.file_text.Bind(wx.EVT_TEXT, self.handle_file_changed)
+ file_sizer.Add(self.file_text, proportion=1, flag=wx.EXPAND, border=5)
+ self.file_browse_button = wx.Button(file_panel, label="Browse")
+ self.file_browse_button.Bind(wx.EVT_BUTTON, self.handle_file_browse_pressed)
+ file_sizer.Add(self.file_browse_button, flag=wx.EXPAND, border=5)
+
+ options_panel = wx.Panel(self)
+ root_sizer.Add(options_panel, flag=wx.EXPAND, border=5)
+ options_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ options_panel.SetSizer(options_sizer)
+ self.silence = wx.CheckBox(options_panel, label="Silence")
+ options_sizer.Add(self.silence, proportion=1, flag=wx.EXPAND, border=5)
+ self.improve_gif = wx.CheckBox(options_panel, label="Improve GIF")
+ options_sizer.Add(self.improve_gif, proportion=1, flag=wx.EXPAND, border=5)
+ self.improve_gif.Disable()
+
+ self.run_panel = wx.Panel(self)
+ root_sizer.Add(self.run_panel, flag=wx.EXPAND, border=5)
+ run_sizer = wx.BoxSizer(wx.HORIZONTAL)
+ self.run_panel.SetSizer(run_sizer)
+ run_button = wx.Button(self.run_panel, label="Run")
+ run_button.Bind(wx.EVT_BUTTON, self.handle_run_pressed)
+ run_sizer.Add(run_button, proportion=1, flag=wx.EXPAND, border=5)
+ run_preview_button = wx.Button(self.run_panel, label="Run && Preview")
+ run_preview_button.Bind(wx.EVT_BUTTON, self.handle_run_preview_pressed)
+ run_sizer.Add(run_preview_button, proportion=1, flag=wx.EXPAND, border=5)
+ run_quit_button = wx.Button(self.run_panel, label="Run && Quit")
+ run_quit_button.Bind(wx.EVT_BUTTON, self.handle_run_quit_pressed)
+ run_sizer.Add(run_quit_button, proportion=1, flag=wx.EXPAND, border=5)
+ self.run_panel.Disable()
+
+ self.logs = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_READONLY)
+ root_sizer.Add(self.logs, proportion=1, flag=wx.EXPAND, border=5)
+
+ self.Disable()
+
+ def handle_file_changed(self, _):
+ path = self.file_text.GetValue()
+ (folder, name) = os.path.split(path)
+ try:
+ os.stat(folder)
+ self.run_panel.Enable()
+ except FileNotFoundError:
+ self.run_panel.Disable()
+ (name, ext) = os.path.splitext(name)
+ if ext == '.gif':
+ self.improve_gif.Enable()
+ else:
+ self.improve_gif.SetValue(False)
+ self.improve_gif.Disable()
+
+ def handle_file_browse_pressed(self, _):
+ dialog = wx.FileDialog(self, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
+ if dialog.ShowModal() == wx.ID_OK:
+ self.file_text.SetValue(dialog.GetPath())
+
+ def handle_run_pressed(self, _, callback=lambda _: None):
+ self.logs.Clear()
+ self.run_panel.Disable()
+ real_args = self.get_ffmpeg_args()
+ if self.silence.GetValue():
+ real_args += ['-an']
+ if self.improve_gif.GetValue():
+ filter_before = '[0:v] '
+ filter_after = 'split [a][b];[a] palettegen [p];[b][p] paletteuse'
+ filter_during = ''
+ if '-vf' in real_args:
+ for i in range(len(real_args) - 1):
+ [a, b] = real_args[i:i + 2]
+ if a == '-vf':
+ filter_during = b + ','
+ real_args[i:i + 2] = []
+ break
+ real_args += ['-filter_complex', filter_before + filter_during + filter_after]
+ args = ['ffmpeg', '-hide_banner', '-y', '-i', self.input_path] + real_args + [self.file_text.GetValue()]
+ print(args)
+
+ def run():
+ # noinspection PyArgumentList
+ proc = subprocess.Popen(args, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+ text=True)
+ while proc.poll() is None:
+ out_data = proc.stdout.readline()
+ if out_data != '':
+ wx.CallAfter(lambda: self.add_log(out_data))
+ wx.CallAfter(lambda: self.run_panel.Enable())
+ wx.CallAfter(lambda: callback(proc.returncode))
+
+ threading.Thread(target=run).start()
+
+ def handle_run_preview_pressed(self, _event):
+ def preview(code):
+ if code == 0:
+ out_file = self.file_text.GetValue()
+ subprocess.Popen(['ffplay', '-autoexit', out_file])
+
+ self.handle_run_pressed(_event, callback=preview)
+
+ def handle_run_quit_pressed(self, _event):
+ def quit(code):
+ if code == 0:
+ parent = self.GetTopLevelParent()
+ parent.Close(True)
+
+ self.handle_run_pressed(_event, callback=quit)
+
+ def set_input_path(self, path, data):
+ if data is None:
+ self.input_path = None
+ self.Disable()
+ else:
+ self.input_path = path
+ self.Enable()
+
+ def add_log(self, data):
+ self.logs.AppendText(data)
diff --git a/sources.py b/sources.py
index b9c8e42..f15f3b3 100644
--- a/sources.py
+++ b/sources.py
@@ -82,11 +82,11 @@ class SourcesPanel(wx.Panel):
threading.Thread(target=download).start()
def handle_file_browse_pressed(self, _):
- dialog = wx.FileDialog(self, message="message")
+ dialog = wx.FileDialog(self, style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
if dialog.ShowModal() == wx.ID_OK:
self.file_text.SetValue(dialog.GetPath())
- def handle_file_changed(self, event):
+ def handle_file_changed(self, _event):
result = subprocess.run([
'ffprobe', '-v', 'error', '-of', 'json',
'-show_entries', 'format=start_time,duration:stream=index,codec_type,avg_frame_rate,width,height',
diff --git a/vidslice.bat b/vidslice.bat
new file mode 100644
index 0000000..139fe3b
--- /dev/null
+++ b/vidslice.bat
@@ -0,0 +1 @@
+D:\Melody\Projects\vidslice\venv\Scripts\python.exe D:/Melody/Projects/vidslice/vidslice.py %1
diff --git a/vidslice.py b/vidslice.py
index 267c064..6cf157c 100644
--- a/vidslice.py
+++ b/vidslice.py
@@ -1,37 +1,54 @@
+import sys
+
import wx
+import wx.adv
-from sources import SourcesPanel
from options import OptionsPanel
+from output import OutputPanel
+from sources import SourcesPanel
-class HelloFrame(wx.Frame):
+class VidsliceFrame(wx.Frame):
"""
- A Frame that says Hello World
+ A Frame that contains vidslice logic
"""
def __init__(self, *args, **kw):
# ensure the parent's __init__ is called
- super(HelloFrame, self).__init__(*args, **kw)
+ super(VidsliceFrame, self).__init__(*args, **kw)
- main_sizer = wx.BoxSizer(wx.VERTICAL)
+ root_sizer = wx.BoxSizer(wx.VERTICAL)
+ self.SetSizer(root_sizer)
+ main = wx.Panel(self)
+ root_sizer.Add(main, proportion=1, flag=wx.EXPAND, border=5)
+ main_sizer = wx.GridBagSizer(5, 5)
+ main.SetSizer(main_sizer)
# set up sources panel
- self.sources_panel = SourcesPanel(self)
- main_sizer.Add(self.sources_panel, proportion=0, flag=wx.EXPAND, border=5)
+ self.sources_panel = SourcesPanel(main)
+ main_sizer.Add(self.sources_panel, wx.GBPosition(0, 0), wx.GBSpan(1, 2), flag=wx.EXPAND)
- # create a panel in the frame
- self.options_panel = OptionsPanel(self)
- main_sizer.Add(self.options_panel, proportion=1, flag=wx.EXPAND, border=5)
- self.sources_panel.on_update(lambda data: self.options_panel.update(self.sources_panel.get_file(), data))
+ # set up options panel
+ self.options_panel = OptionsPanel(main)
+ main_sizer.Add(self.options_panel, wx.GBPosition(1, 0), flag=wx.EXPAND)
+ main_sizer.AddGrowableRow(1, proportion=1)
+ self.sources_panel.on_update(self.options_panel.update)
+
+ # set up output panel
+ self.output_panel = OutputPanel(main, get_ffmpeg_args=self.options_panel.ffmpeg_opts)
+ main_sizer.Add(self.output_panel, wx.GBPosition(1, 1), flag=wx.EXPAND)
+ main_sizer.AddGrowableCol(1, proportion=1)
+ self.sources_panel.on_update(lambda data: self.output_panel.set_input_path(self.sources_panel.get_file(), data))
# create a menu bar
self.make_menu_bar()
- # main_sizer.SetSizeHints(self)
- self.SetSizer(main_sizer)
- size = main_sizer.GetMinSize()
+ size = root_sizer.GetMinSize()
self.SetMinClientSize(size)
+ if len(sys.argv) > 1:
+ self.sources_panel.file_text.SetValue(sys.argv[1])
+
def make_menu_bar(self):
"""
A menu bar is composed of menus, which are composed of menu items.
@@ -43,8 +60,7 @@ class HelloFrame(wx.Frame):
file_menu = wx.Menu()
# The "\t..." syntax defines an accelerator key that also triggers
# the same event
- hello_item = file_menu.Append(-1, "&Hello...\tCtrl-H",
- "Help string shown in status bar for this menu item")
+ hello_item = file_menu.Append(-1, "&Hello...\tCtrl-H")
file_menu.AppendSeparator()
# When using a stock ID we don't need to specify the menu item's
# label
@@ -82,15 +98,20 @@ class HelloFrame(wx.Frame):
def on_about(self, event):
"""Display an About Dialog"""
- wx.MessageBox("This is a wxPython Hello World sample",
- "About Hello World 2",
- wx.OK | wx.ICON_INFORMATION)
+ info = wx.adv.AboutDialogInfo()
+ info.SetName("vidslice")
+ info.SetDescription("video manipulator wrapping youtube-dl and ffmpeg")
+ info.SetDevelopers(["Melody Horn"])
+ info.SetWebSite("https://www.boringcactus.com")
+
+ wx.adv.AboutBox(info)
if __name__ == '__main__':
# When this module is run (not imported) then create the app, the
# frame, show it, and start the event loop.
app = wx.App()
- frm = HelloFrame(None, title='Hello World 2')
+ frm = VidsliceFrame(None, title='vidslice')
+ app.SetTopWindow(frm)
frm.Show()
app.MainLoop()