PyVideoKit-GUI 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,108 @@
1
+ from pathlib import Path
2
+
3
+ from PySide6.QtCore import Qt
4
+ from PySide6.QtWidgets import (
5
+ QFileDialog,
6
+ QHBoxLayout,
7
+ QLabel,
8
+ QLineEdit,
9
+ QProgressBar,
10
+ QPushButton,
11
+ QVBoxLayout,
12
+ QWidget,
13
+ )
14
+
15
+ from pyvideokit_gui._worker import Worker
16
+
17
+ _VIDEO_FILTER = "Video files (*.mkv *.mp4 *.mov *.avi *.webm);;All files (*)"
18
+
19
+
20
+ class BasePanel(QWidget):
21
+ def __init__(self, parent=None):
22
+ super().__init__(parent)
23
+ self._worker = None
24
+ root = QVBoxLayout(self)
25
+ root.setAlignment(Qt.AlignTop)
26
+ root.setSpacing(10)
27
+ root.setContentsMargins(20, 20, 20, 20)
28
+ self._build_ui(root)
29
+ root.addStretch()
30
+ self._build_run_section(root)
31
+
32
+ def _build_ui(self, layout: QVBoxLayout):
33
+ raise NotImplementedError
34
+
35
+ def _run(self):
36
+ raise NotImplementedError
37
+
38
+ # ── file picker helpers ───────────────────────────────────────────────
39
+
40
+ def _input_row(self, layout, label="Input file", filter=_VIDEO_FILTER) -> QLineEdit:
41
+ layout.addWidget(QLabel(label))
42
+ row = QHBoxLayout()
43
+ le = QLineEdit()
44
+ le.setPlaceholderText("Select a file…")
45
+ btn = QPushButton("Browse…")
46
+ row.addWidget(le)
47
+ row.addWidget(btn)
48
+ layout.addLayout(row)
49
+ btn.clicked.connect(lambda: self._browse_open(le, label, filter))
50
+ return le
51
+
52
+ def _output_row(self, layout, label="Output (optional)") -> QLineEdit:
53
+ layout.addWidget(QLabel(label))
54
+ row = QHBoxLayout()
55
+ le = QLineEdit()
56
+ le.setPlaceholderText("Leave empty for default…")
57
+ btn = QPushButton("Browse…")
58
+ row.addWidget(le)
59
+ row.addWidget(btn)
60
+ layout.addLayout(row)
61
+ btn.clicked.connect(lambda: self._browse_save(le, label))
62
+ return le
63
+
64
+ def _browse_open(self, le: QLineEdit, title: str, filter: str):
65
+ path, _ = QFileDialog.getOpenFileName(self, title, "", filter)
66
+ if path:
67
+ le.setText(path)
68
+
69
+ def _browse_save(self, le: QLineEdit, title: str):
70
+ path, _ = QFileDialog.getSaveFileName(self, title, "")
71
+ if path:
72
+ le.setText(path)
73
+
74
+ # ── worker ────────────────────────────────────────────────────────────
75
+
76
+ def _start_worker(self, fn, *args, **kwargs):
77
+ self._progress.setValue(0)
78
+ self._status.setText("Running…")
79
+ self._run_btn.setEnabled(False)
80
+ self._worker = Worker(fn, args, kwargs)
81
+ self._worker.progress.connect(lambda pct: self._progress.setValue(int(pct)))
82
+ self._worker.finished.connect(self._on_finished)
83
+ self._worker.error.connect(self._on_error)
84
+ self._worker.start()
85
+
86
+ # ── run section ───────────────────────────────────────────────────────
87
+
88
+ def _build_run_section(self, layout):
89
+ self._run_btn = QPushButton("▶ Run")
90
+ self._run_btn.setFixedHeight(36)
91
+ self._run_btn.clicked.connect(self._run)
92
+ self._progress = QProgressBar()
93
+ self._progress.setRange(0, 100)
94
+ self._progress.setValue(0)
95
+ self._status = QLabel("")
96
+ layout.addWidget(self._run_btn)
97
+ layout.addWidget(self._progress)
98
+ layout.addWidget(self._status)
99
+
100
+ def _on_finished(self, result):
101
+ self._progress.setValue(100)
102
+ self._status.setText(f"✅ Done → {Path(str(result)).name}")
103
+ self._run_btn.setEnabled(True)
104
+
105
+ def _on_error(self, msg):
106
+ self._progress.setValue(0)
107
+ self._status.setText(f"❌ {msg}")
108
+ self._run_btn.setEnabled(True)
@@ -0,0 +1,18 @@
1
+ import sys
2
+
3
+ from PySide6.QtWidgets import QApplication
4
+
5
+ from pyvideokit_gui._main_window import MainWindow
6
+
7
+
8
+ def main():
9
+ app = QApplication(sys.argv)
10
+ app.setApplicationName("PyVideoKit")
11
+ app.setStyle("Fusion")
12
+ window = MainWindow()
13
+ window.show()
14
+ sys.exit(app.exec())
15
+
16
+
17
+ if __name__ == "__main__":
18
+ main()
@@ -0,0 +1,27 @@
1
+ from PySide6.QtWidgets import QMainWindow, QTabWidget
2
+
3
+ from pyvideokit_gui.concat_panel import ConcatPanel
4
+ from pyvideokit_gui.convert_panel import ConvertPanel
5
+ from pyvideokit_gui.extract_audio_panel import ExtractAudioPanel
6
+ from pyvideokit_gui.fade_panel import FadePanel
7
+ from pyvideokit_gui.trim_panel import TrimPanel
8
+ from pyvideokit_gui.vhs_panel import VhsPanel
9
+ from pyvideokit_gui.youtube_panel import YouTubePanel
10
+
11
+
12
+ class MainWindow(QMainWindow):
13
+ def __init__(self):
14
+ super().__init__()
15
+ self.setWindowTitle("PyVideoKit")
16
+ self.setMinimumSize(660, 460)
17
+
18
+ tabs = QTabWidget()
19
+ tabs.addTab(ConvertPanel(), "🎞️ Convert to FFV1")
20
+ tabs.addTab(TrimPanel(), "✂️ Trim")
21
+ tabs.addTab(ConcatPanel(), "🔗 Concat")
22
+ tabs.addTab(FadePanel(), "🎬 Fade")
23
+ tabs.addTab(VhsPanel(), "📼 VHS Effect")
24
+ tabs.addTab(ExtractAudioPanel(), "🔊 Extract Audio")
25
+ tabs.addTab(YouTubePanel(), "📺 YouTube")
26
+
27
+ self.setCentralWidget(tabs)
@@ -0,0 +1,21 @@
1
+ from PySide6.QtCore import QThread, Signal
2
+
3
+
4
+ class Worker(QThread):
5
+ progress = Signal(float)
6
+ finished = Signal(object)
7
+ error = Signal(str)
8
+
9
+ def __init__(self, fn, args=(), kwargs=None):
10
+ super().__init__()
11
+ self._fn = fn
12
+ self._args = args
13
+ self._kwargs = dict(kwargs or {})
14
+ self._kwargs["on_progress"] = self.progress.emit
15
+
16
+ def run(self):
17
+ try:
18
+ result = self._fn(*self._args, **self._kwargs)
19
+ self.finished.emit(result)
20
+ except Exception as e:
21
+ self.error.emit(str(e))
@@ -0,0 +1,56 @@
1
+ from pathlib import Path
2
+
3
+ from PySide6.QtWidgets import (
4
+ QAbstractItemView,
5
+ QFileDialog,
6
+ QHBoxLayout,
7
+ QLabel,
8
+ QListWidget,
9
+ QPushButton,
10
+ QVBoxLayout,
11
+ )
12
+
13
+ from pyvideokit_gui._base_panel import BasePanel
14
+ from pyvideokit_libs import join_videos
15
+
16
+
17
+ class ConcatPanel(BasePanel):
18
+ def _build_ui(self, layout: QVBoxLayout):
19
+ layout.addWidget(QLabel("Video files (add at least 2, drag to reorder)"))
20
+
21
+ self._list = QListWidget()
22
+ self._list.setDragDropMode(QAbstractItemView.InternalMove)
23
+ self._list.setMinimumHeight(120)
24
+ layout.addWidget(self._list)
25
+
26
+ btns = QHBoxLayout()
27
+ add_btn = QPushButton("Add files…")
28
+ rm_btn = QPushButton("Remove selected")
29
+ btns.addWidget(add_btn)
30
+ btns.addWidget(rm_btn)
31
+ layout.addLayout(btns)
32
+
33
+ add_btn.clicked.connect(self._add_files)
34
+ rm_btn.clicked.connect(self._remove_selected)
35
+
36
+ self._output = self._output_row(layout, "Output (optional)")
37
+
38
+ def _add_files(self):
39
+ paths, _ = QFileDialog.getOpenFileNames(
40
+ self, "Add video files", "",
41
+ "Video files (*.mkv *.mp4 *.mov *.avi *.webm);;All files (*)",
42
+ )
43
+ for p in paths:
44
+ self._list.addItem(p)
45
+
46
+ def _remove_selected(self):
47
+ for item in self._list.selectedItems():
48
+ self._list.takeItem(self._list.row(item))
49
+
50
+ def _run(self):
51
+ paths = [Path(self._list.item(i).text()) for i in range(self._list.count())]
52
+ if len(paths) < 2:
53
+ self._status.setText("❌ Add at least 2 video files.")
54
+ return
55
+ output = self._output.text().strip() or None
56
+ self._start_worker(join_videos, paths, output=output)
@@ -0,0 +1,27 @@
1
+ from pathlib import Path
2
+
3
+ from PySide6.QtWidgets import QLabel, QSpinBox, QVBoxLayout
4
+
5
+ from pyvideokit_gui._base_panel import BasePanel
6
+ from pyvideokit_libs import convert_to_ffv1
7
+
8
+
9
+ class ConvertPanel(BasePanel):
10
+ def _build_ui(self, layout: QVBoxLayout):
11
+ self._input = self._input_row(layout, "Input file")
12
+
13
+ layout.addWidget(QLabel("FPS"))
14
+ self._fps = QSpinBox()
15
+ self._fps.setRange(1, 240)
16
+ self._fps.setValue(60)
17
+ layout.addWidget(self._fps)
18
+
19
+ self._output = self._output_row(layout, "Output MKV (optional)")
20
+
21
+ def _run(self):
22
+ path = self._input.text().strip()
23
+ if not path:
24
+ self._status.setText("❌ Select an input file.")
25
+ return
26
+ output = self._output.text().strip() or None
27
+ self._start_worker(convert_to_ffv1, Path(path), fps=self._fps.value(), output=output)
@@ -0,0 +1,20 @@
1
+ from pathlib import Path
2
+
3
+ from PySide6.QtWidgets import QVBoxLayout
4
+
5
+ from pyvideokit_gui._base_panel import BasePanel
6
+ from pyvideokit_libs import extract_audio
7
+
8
+
9
+ class ExtractAudioPanel(BasePanel):
10
+ def _build_ui(self, layout: QVBoxLayout):
11
+ self._input = self._input_row(layout, "Input file")
12
+ self._output = self._output_row(layout, "Output WAV (optional)")
13
+
14
+ def _run(self):
15
+ path = self._input.text().strip()
16
+ if not path:
17
+ self._status.setText("❌ Select an input file.")
18
+ return
19
+ output = self._output.text().strip() or None
20
+ self._start_worker(extract_audio, Path(path), output=output)
@@ -0,0 +1,59 @@
1
+ from pathlib import Path
2
+
3
+ from PySide6.QtWidgets import QDoubleSpinBox, QHBoxLayout, QLabel, QSpinBox, QVBoxLayout
4
+
5
+ from pyvideokit_gui._base_panel import BasePanel
6
+ from pyvideokit_libs import fade_video
7
+
8
+
9
+ class FadePanel(BasePanel):
10
+ def _build_ui(self, layout: QVBoxLayout):
11
+ self._input = self._input_row(layout, "Input file (FFV1)")
12
+
13
+ row = QHBoxLayout()
14
+
15
+ fi_col = QVBoxLayout()
16
+ fi_col.addWidget(QLabel("Fade-in (seconds, 0 = disabled)"))
17
+ self._fade_in = QDoubleSpinBox()
18
+ self._fade_in.setRange(0, 60)
19
+ self._fade_in.setDecimals(2)
20
+ fi_col.addWidget(self._fade_in)
21
+
22
+ fo_col = QVBoxLayout()
23
+ fo_col.addWidget(QLabel("Fade-out (seconds, 0 = disabled)"))
24
+ self._fade_out = QDoubleSpinBox()
25
+ self._fade_out.setRange(0, 60)
26
+ self._fade_out.setDecimals(2)
27
+ fo_col.addWidget(self._fade_out)
28
+
29
+ row.addLayout(fi_col)
30
+ row.addLayout(fo_col)
31
+ layout.addLayout(row)
32
+
33
+ layout.addWidget(QLabel("FPS"))
34
+ self._fps = QSpinBox()
35
+ self._fps.setRange(1, 240)
36
+ self._fps.setValue(60)
37
+ layout.addWidget(self._fps)
38
+
39
+ self._output = self._output_row(layout, "Output (optional)")
40
+
41
+ def _run(self):
42
+ path = self._input.text().strip()
43
+ if not path:
44
+ self._status.setText("❌ Select an input file.")
45
+ return
46
+ fi = self._fade_in.value() or None
47
+ fo = self._fade_out.value() or None
48
+ if not fi and not fo:
49
+ self._status.setText("❌ Set at least one fade duration.")
50
+ return
51
+ output = self._output.text().strip() or None
52
+ self._start_worker(
53
+ fade_video,
54
+ Path(path),
55
+ fade_in=fi,
56
+ fade_out=fo,
57
+ fps=self._fps.value(),
58
+ output=output,
59
+ )
@@ -0,0 +1,48 @@
1
+ from pathlib import Path
2
+
3
+ from PySide6.QtWidgets import QDoubleSpinBox, QHBoxLayout, QLabel, QVBoxLayout
4
+
5
+ from pyvideokit_gui._base_panel import BasePanel
6
+ from pyvideokit_libs import trim_video
7
+
8
+
9
+ class TrimPanel(BasePanel):
10
+ def _build_ui(self, layout: QVBoxLayout):
11
+ self._input = self._input_row(layout, "Input file")
12
+
13
+ row = QHBoxLayout()
14
+
15
+ start_col = QVBoxLayout()
16
+ start_col.addWidget(QLabel("Start (seconds)"))
17
+ self._start = QDoubleSpinBox()
18
+ self._start.setRange(0, 999999)
19
+ self._start.setDecimals(2)
20
+ start_col.addWidget(self._start)
21
+
22
+ end_col = QVBoxLayout()
23
+ end_col.addWidget(QLabel("End (seconds)"))
24
+ self._end = QDoubleSpinBox()
25
+ self._end.setRange(0, 999999)
26
+ self._end.setDecimals(2)
27
+ self._end.setValue(60)
28
+ end_col.addWidget(self._end)
29
+
30
+ row.addLayout(start_col)
31
+ row.addLayout(end_col)
32
+ layout.addLayout(row)
33
+
34
+ self._output = self._output_row(layout, "Output (optional)")
35
+
36
+ def _run(self):
37
+ path = self._input.text().strip()
38
+ if not path:
39
+ self._status.setText("❌ Select an input file.")
40
+ return
41
+ output = self._output.text().strip() or None
42
+ self._start_worker(
43
+ trim_video,
44
+ Path(path),
45
+ self._start.value(),
46
+ self._end.value(),
47
+ output=output,
48
+ )
@@ -0,0 +1,20 @@
1
+ from pathlib import Path
2
+
3
+ from PySide6.QtWidgets import QVBoxLayout
4
+
5
+ from pyvideokit_gui._base_panel import BasePanel
6
+ from pyvideokit_libs import apply_vhs_effect
7
+
8
+
9
+ class VhsPanel(BasePanel):
10
+ def _build_ui(self, layout: QVBoxLayout):
11
+ self._input = self._input_row(layout, "Input file (must have audio)")
12
+ self._output = self._output_row(layout, "Output MKV (optional)")
13
+
14
+ def _run(self):
15
+ path = self._input.text().strip()
16
+ if not path:
17
+ self._status.setText("❌ Select an input file.")
18
+ return
19
+ output = self._output.text().strip() or None
20
+ self._start_worker(apply_vhs_effect, Path(path), output=output)
@@ -0,0 +1,20 @@
1
+ from pathlib import Path
2
+
3
+ from PySide6.QtWidgets import QVBoxLayout
4
+
5
+ from pyvideokit_gui._base_panel import BasePanel
6
+ from pyvideokit_libs import prepare_youtube
7
+
8
+
9
+ class YouTubePanel(BasePanel):
10
+ def _build_ui(self, layout: QVBoxLayout):
11
+ self._input = self._input_row(layout, "Input file (FFV1)")
12
+ self._output = self._output_row(layout, "Output MOV (optional)")
13
+
14
+ def _run(self):
15
+ path = self._input.text().strip()
16
+ if not path:
17
+ self._status.setText("❌ Select an input file.")
18
+ return
19
+ output = self._output.text().strip() or None
20
+ self._start_worker(prepare_youtube, Path(path), output=output)