~boringcactus/vidslice

c81da475babdda24000488da62b33048d3239947 — Melody Horn 1 year, 8 months ago 21e1d38
add preview
2 files changed, 101 insertions(+), 7 deletions(-)

A preview.py
M vidslice.py
A preview.py => preview.py +89 -0
@@ 0,0 1,89 @@
import subprocess
import tempfile
import threading
from tkinter import *
from tkinter import ttk

from options import FFmpegOptions


class PreviewPanel(ttk.LabelFrame):
    def __init__(self, *args, get_ffmpeg_args=lambda: FFmpegOptions([], []), get_frame_count=lambda: 0, **kw):
        super(PreviewPanel, self).__init__(*args, text='Preview', **kw)
        self.input_path = None
        self.get_ffmpeg_args = get_ffmpeg_args
        self.get_frame_count = get_frame_count

        def button(text, command, column):
            ttk.Button(self, text=text, command=command).grid(column=column, row=0, sticky=(N, W, S, E))
            self.columnconfigure(column, weight=1)

        button("Preview Start", self.preview_start, 0)
        button("Preview Middle", self.preview_middle, 1)
        button("Preview End", self.preview_end, 2)

        self.image = None
        self.image_label = ttk.Label(self, anchor='center')
        self.image_label.grid(column=0, row=1, columnspan=3, sticky=(N, W, S, E))
        self.rowconfigure(1, weight=1)

        self.enable(False)

    def preview_at(self, offset):
        offset = int(offset)
        self.enable(False)
        real_args = self.get_ffmpeg_args()
        input_args = real_args.input
        width = self.image_label.winfo_width()
        height = self.image_label.winfo_height()
        real_args.vf += [
            rf'select=eq(n\,{offset})',
            f'scale=w={width}:h={height}:force_original_aspect_ratio=decrease'
        ]
        real_args.output += ['-frames:v', '1']
        output_args = real_args.output_with_vf()

        def run():
            _, output_path = tempfile.mkstemp(suffix='.png')
            args = ['ffmpeg', '-hide_banner', '-v', 'warning', '-y'] + input_args + \
                   ['-i', self.input_path] + output_args + [output_path]
            print(args)
            # noinspection PyArgumentList
            proc = subprocess.Popen(args, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
                                    stderr=subprocess.STDOUT, text=True, creationflags=subprocess.CREATE_NO_WINDOW)
            while proc.poll() is None:
                out_data = proc.stdout.readline()
                if out_data != '':
                    print(out_data, end='')
            self.enable(True)
            self.image = PhotoImage(file=output_path)
            self.image_label['image'] = self.image

        threading.Thread(target=run).start()

    def preview_start(self, *args):
        self.preview_at(0)

    def preview_middle(self, *args):
        self.preview_at(self.get_frame_count() / 2)

    def preview_end(self, *args):
        self.preview_at(self.get_frame_count() - 1)

    def enable(self, enabled):
        state = 'disabled'
        if enabled:
            state = '!' + state
        self.state([state])
        for child in self.winfo_children():
            try:
                child.state([state])
            except AttributeError:
                pass

    def set_input_path(self, path, data):
        self.enable(data is not None)
        if data is None:
            self.input_path = None
        else:
            self.input_path = path

M vidslice.py => vidslice.py +12 -7
@@ 7,6 7,7 @@ from tkinter import ttk

from options import OptionsPanel
from output import OutputPanel
from preview import PreviewPanel
from sources import SourcesPanel, update_ytdl

VERSION = "1.6"


@@ 45,20 46,24 @@ class VidsliceFrame:
        root.columnconfigure(0, weight=1)
        root.rowconfigure(0, weight=1)

        # set up sources panel
        self.sources_panel = SourcesPanel(mainframe)
        self.sources_panel.grid(column=0, row=0, columnspan=2, sticky=(W, E), padx=5, pady=5)
        self.sources_panel.grid(column=0, row=0, columnspan=2, sticky=(W, E, N, S), padx=5, pady=5)

        # set up options panel
        self.options_panel = OptionsPanel(mainframe)
        self.options_panel.grid(column=0, row=1, sticky=(W, E, N), padx=5, pady=5)
        mainframe.rowconfigure(1, weight=1)
        self.options_panel.grid(column=0, row=1, columnspan=2, sticky=(W, N, S), padx=5, pady=5)
        self.sources_panel.on_update(self.options_panel.update_info)

        # set up output panel
        self.preview_panel = PreviewPanel(mainframe, get_ffmpeg_args=self.options_panel.ffmpeg_opts,
                                          get_frame_count=self.options_panel.frame_count)
        self.preview_panel.grid(column=0, row=2, sticky=(W, E, N, S), padx=5, pady=5)
        mainframe.rowconfigure(2, weight=1)
        mainframe.columnconfigure(0, weight=2)
        self.sources_panel.on_update(
            lambda data: self.preview_panel.set_input_path(self.sources_panel.get_file(), data))

        self.output_panel = OutputPanel(mainframe, get_ffmpeg_args=self.options_panel.ffmpeg_opts,
                                        get_frame_count=self.options_panel.frame_count)
        self.output_panel.grid(column=1, row=1, sticky=(W, E, N, S), padx=5, pady=5)
        self.output_panel.grid(column=1, row=2, sticky=(W, E, N, S), padx=5, pady=5)
        mainframe.columnconfigure(1, weight=1)
        self.sources_panel.on_update(lambda data: self.output_panel.set_input_path(self.sources_panel.get_file(), data))