~boringcactus/vidslice

ref: 23132238f9baa9a061a445e2d786a32790aa60a7 vidslice/sources.py -rw-r--r-- 5.6 KiB
23132238 — Melody Horn use -ss as an input option for *far* better efficiency 3 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import glob
import json
import os
import subprocess
import tempfile
import threading

import wx


def has_ytdl():
    try:
        subprocess.run(["youtube-dl", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        return True
    except FileNotFoundError:
        return False


def update_ytdl(parent_win):
    try:
        youtube_dl_found = subprocess.run(['where', 'youtube-dl'], stdout=subprocess.PIPE, text=True)
    except FileNotFoundError:
        youtube_dl_found = subprocess.run(['which', 'youtube-dl'], stdout=subprocess.PIPE, text=True)
    if youtube_dl_found.returncode != 0:
        def poll():
            answer = wx.MessageBox("Could not find youtube-dl. Open vidslice README?", "Error", wx.YES_NO, parent_win)
            if answer == wx.YES:
                import webbrowser
                webbrowser.open("https://github.com/boringcactus/vidslice/blob/master/README.md")
            return

        wx.CallAfter(poll)
    youtube_dl_path = youtube_dl_found.stdout.split("\n")[0]
    old_mtime = os.stat(youtube_dl_path).st_mtime
    proc = subprocess.run(["youtube-dl", "-U"], stdout=subprocess.PIPE, text=True)
    if not proc.stdout.startswith("youtube-dl is up-to-date"):
        while os.stat(youtube_dl_path).st_mtime == old_mtime:
            from time import sleep
            sleep(0.25)
    wx.CallAfter(lambda: wx.MessageBox("Updated youtube-dl successfully", "Complete", wx.OK, parent_win))


class SourcesPanel(wx.Panel):
    """
    A Panel representing source info
    """

    def __init__(self, *args, **kw):
        super(SourcesPanel, self).__init__(*args, **kw)
        self.update_listeners = []

        root_sizer = wx.StaticBoxSizer(wx.VERTICAL, self, label="Sources")
        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)

        if has_ytdl():
            main_sizer.Add(wx.StaticText(main, label="URL"), wx.GBPosition(0, 0), flag=wx.EXPAND)
            self.url_text = wx.TextCtrl(main)
            main_sizer.Add(self.url_text, wx.GBPosition(0, 1), flag=wx.EXPAND)
            self.url_download_button = wx.Button(main, label="Download")
            self.url_download_button.Bind(wx.EVT_BUTTON, self.handle_url_download_pressed)
            main_sizer.Add(self.url_download_button, wx.GBPosition(0, 2), flag=wx.EXPAND)
        else:
            no_ytdl_label = wx.StaticText(main, label="Could not find youtube-dl, can't download videos automatically")
            main_sizer.Add(no_ytdl_label, wx.GBPosition(0, 0), wx.GBSpan(1, 3), flag=wx.EXPAND)

        main_sizer.Add(wx.StaticText(main, label="File"), wx.GBPosition(1, 0), flag=wx.EXPAND)
        self.file_text = wx.TextCtrl(main)
        self.file_text.Bind(wx.EVT_TEXT, self.handle_file_changed)
        main_sizer.Add(self.file_text, wx.GBPosition(1, 1), flag=wx.EXPAND)
        self.file_browse_button = wx.Button(main, label="Browse")
        self.file_browse_button.Bind(wx.EVT_BUTTON, self.handle_file_browse_pressed)
        main_sizer.Add(self.file_browse_button, wx.GBPosition(1, 2), flag=wx.EXPAND)

        self.status_label = wx.StaticText(main, label="Status: Select a file")
        main_sizer.Add(self.status_label, wx.GBPosition(2, 0), wx.GBSpan(1, 3))

        main_sizer.AddGrowableCol(1, proportion=1)

    def set_status(self, text):
        self.status_label.SetLabel("Status: " + text)

    def handle_url_download_pressed(self, _):
        self.set_status("Downloading...")

        def download():
            file = tempfile.NamedTemporaryFile(delete=False)
            # noinspection PyArgumentList
            proc = subprocess.Popen([
                'youtube-dl', '-o', file.name + '.%(ext)s', self.url_text.GetValue()
            ], stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
            while proc.poll() is None:
                out_data = proc.stdout.readline()
                if out_data != '':
                    wx.CallAfter(lambda: self.set_status("Downloading: " + out_data.strip()))
            if proc.returncode == 0:
                output_file = glob.glob(glob.escape(file.name) + '.*')[0]
                wx.CallAfter(lambda: self.set_status("Downloaded!"))
                wx.CallAfter(lambda: self.file_text.SetValue(output_file))
            else:
                error = ''.join(proc.stderr.readlines()).strip()
                wx.CallAfter(lambda: self.set_status("Couldn't download: " + error))

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

    def handle_file_browse_pressed(self, _):
        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):
        result = subprocess.run([
            'ffprobe', '-v', 'error', '-of', 'json',
            '-show_entries', 'format=start_time,duration:stream=index,codec_type,avg_frame_rate,width,height',
            self.file_text.GetValue()
        ], capture_output=True, text=True)
        if result.returncode == 0:
            ffprobe_data = json.loads(result.stdout)
            self.set_status("Successfully loaded media info")
            for listener in self.update_listeners:
                listener(ffprobe_data)
        else:
            self.set_status("Failed to load media info: " + result.stderr)
            for listener in self.update_listeners:
                listener(None)

    def on_update(self, callback):
        self.update_listeners.append(callback)

    def get_file(self):
        return self.file_text.GetValue()