From b6a61b33efaca4519eb8c4949766d42b535da0ce Mon Sep 17 00:00:00 2001 From: Alvydas Date: Sun, 2 Nov 2025 13:23:47 +0200 Subject: [PATCH 01/12] Refactor project into package modules --- jumpcutter.py | 207 +++-------------------------------------- jumpcutter/__init__.py | 6 ++ jumpcutter/core.py | 109 ++++++++++++++++++++++ jumpcutter/gui.py | 118 +++++++++++++++++++++++ 4 files changed, 245 insertions(+), 195 deletions(-) create mode 100644 jumpcutter/__init__.py create mode 100644 jumpcutter/core.py create mode 100644 jumpcutter/gui.py diff --git a/jumpcutter.py b/jumpcutter.py index 009ea04..5434870 100644 --- a/jumpcutter.py +++ b/jumpcutter.py @@ -1,195 +1,12 @@ -import os -import subprocess -import tempfile -import tkinter as tk -from tkinter import filedialog, ttk -from moviepy.editor import VideoFileClip -from pydub import AudioSegment -from pydub.silence import detect_nonsilent -from tqdm import tqdm - - -# ================== CORE LOGIC ================== -def jumpcutter(video_path, output_path, silence_thresh=-40, min_silence_len=500): - if not os.path.isfile(video_path): - print(f"Error: The file '{video_path}' does not exist.") - return - - print(f"\nProcessing: {video_path}") - print("[1/4] Loading video...") - video = VideoFileClip(video_path) - audio = video.audio - - print("[2/4] Exporting audio for silence detection...") - temp_audio_path = "temp_audio.wav" - audio.write_audiofile(temp_audio_path, logger=None) - - print("[3/4] Detecting non-silent audio segments...") - sound = AudioSegment.from_file(temp_audio_path) - nonsilent_parts = detect_nonsilent( - sound, min_silence_len=min_silence_len, silence_thresh=silence_thresh - ) - nonsilent_times = [(start / 1000, end / 1000) for start, end in nonsilent_parts] - - print("[4/4] Creating new video by concatenating non-silent parts...") - clips = [] - for start, end in tqdm(nonsilent_times, desc="Processing video segments"): - segment_duration = end - start - segment = video.subclip(start, end) - - # Pagreitiname segmentą su FFmpeg, jei reikia - if segment_duration < (min_silence_len / 1000): - try: - # Sukuriame laikinus failus - with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_input, \ - tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_output: - - # Išsaugome originalų segmentą - segment.write_videofile(temp_input.name, codec="libx264", audio_codec="aac", logger=None) - - # FFmpeg komanda pagreitinimui - ffmpeg_cmd = [ - "ffmpeg", - "-y", # Perrašyti failą be patvirtinimo - "-i", temp_input.name, - "-filter_complex", f"[0:v]setpts={1}*PTS[v]; [0:a]atempo={1}[a]", - "-map", "[v]", - "-map", "[a]", - "-c:v", "libx264", - "-c:a", "aac", - temp_output.name - ] - - # Paleidžiame FFmpeg - subprocess.run(ffmpeg_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - # Įkeliame pagreitintą segmentą - sped_up_clip = VideoFileClip(temp_output.name) - clips.append(sped_up_clip) - - except Exception as e: - print(f"Klaida pagreitinant segmentą {start}-{end}: {e}. Segmentas praleidžiamas.") - continue - else: - clips.append(segment) - - # Išsaugome naują vaizdo įrašą - from moviepy.editor import concatenate_videoclips - - final_video = concatenate_videoclips(clips) - - final_video.write_videofile(output_path, codec="libx264", audio_codec="aac") - -# ================== GUI ================== -class JumpCutterApp(tk.Tk): - def __init__(self): - super().__init__() - self.title("JumpCutter") - self.geometry("300x300") - self._create_widgets() - - - def _update_thresh_desc(self, value): - value = int(float(value)) - if value <= -55: - desc = "Labai jautru (tyla studijoje)" - elif value <= -45: - desc = "Rekomenduojama kalbiniams įrašams" - elif value <= -35: - desc = "Vidutinis slenkstis (gatvės triukšmas)" - else: - desc = "Mažas jautrumas (triukšminga aplinka)" - self.thresh_desc.config(text=f"{value} dB: {desc}") - - def _update_silence_len_desc(self, value): - self.silence_len_desc.config(text=f"Minimali tyla: {int(float(value))} ms") - - def _create_widgets(self): - # Parametrų įvedimas - main_frame = tk.Frame(self) - main_frame.pack(pady=10) - - # Tylos slenkstis - thresh_frame = tk.Frame(main_frame) - thresh_frame.pack(pady=5) - - tk.Label(thresh_frame, text="Tylos slenkstis:").pack() - self.silence_thresh = tk.Scale( - thresh_frame, - from_=-60, - to=-30, - resolution=5, # Nustatomas 5 db žingsnis - orient="horizontal", - command=self._update_thresh_desc - ) - self.silence_thresh.set(-40) - self.silence_thresh.pack() - - self.thresh_desc = tk.Label(thresh_frame, text="", fg="gray") - self.thresh_desc.pack() - self._update_thresh_desc(-40) - - # Minimali tylos trukmė - silence_len_frame = tk.Frame(main_frame) - silence_len_frame.pack(pady=5) - - tk.Label(silence_len_frame, text="Minimali tylos trukmė:").pack() - self.min_silence_len = tk.Scale( - silence_len_frame, - from_=100, - to=2000, - resolution=100, # Nustatomas 100 ms žingsnis - orient="horizontal", - command=self._update_silence_len_desc - ) - self.min_silence_len.set(500) - self.min_silence_len.pack() - - self.silence_len_desc = tk.Label(silence_len_frame, text="", fg="gray") - self.silence_len_desc.pack() - self._update_silence_len_desc(500) - - - # Failo pasirinkimo mygtukas - tk.Button(self, text="Pasirinkti video", command=self._select_file).pack(pady=20) - - # Progreso juosta - self.progress = ttk.Progressbar(self, mode="indeterminate") - - # Statuso žinutė - self.status_label = tk.Label(self, text="", fg="gray") - self.status_label.pack() - - def _select_file(self): - file_path = filedialog.askopenfilename( - title="Pasirinkite video failą", - filetypes=[("Video Files", "*.mp4 *.mkv *.webm *.mov")] - ) - if file_path: - self._process_video(file_path) - - def _process_video(self, input_path): - self.progress.pack(pady=10) - self.progress.start() - self.status_label.config(text="Apdorojama...", fg="green") - - try: - output_path = f"jumpcutted_{os.path.basename(input_path)}" - jumpcutter( - input_path, - output_path, - silence_thresh=int(self.silence_thresh.get()), - min_silence_len=int(self.min_silence_len.get()), - ) - self.status_label.config(text=f"Baigta! Išsaugota: {output_path}", fg="blue") - except Exception as e: - self.status_label.config(text=f"Klaida: {str(e)}", fg="red") - finally: - self.progress.stop() - self.progress.pack_forget() - - -# ================== PALEIDIMAS ================== -if __name__ == "__main__": - app = JumpCutterApp() - app.mainloop() +"""Entry point for running the Jumpcutter GUI application.""" + +from jumpcutter import JumpCutterApp + + +def main() -> None: + app = JumpCutterApp() + app.mainloop() + + +if __name__ == "__main__": + main() diff --git a/jumpcutter/__init__.py b/jumpcutter/__init__.py new file mode 100644 index 0000000..77e4a29 --- /dev/null +++ b/jumpcutter/__init__.py @@ -0,0 +1,6 @@ +"""Jumpcutter package.""" + +from .core import jumpcutter +from .gui import JumpCutterApp + +__all__ = ["jumpcutter", "JumpCutterApp"] diff --git a/jumpcutter/core.py b/jumpcutter/core.py new file mode 100644 index 0000000..ebfea96 --- /dev/null +++ b/jumpcutter/core.py @@ -0,0 +1,109 @@ +"""Core video processing logic for Jumpcutter.""" + +from __future__ import annotations + +import os +import subprocess +import tempfile +from typing import List, Tuple + +from moviepy.editor import VideoFileClip, concatenate_videoclips +from pydub import AudioSegment +from pydub.silence import detect_nonsilent +from tqdm import tqdm + + +TimeRange = Tuple[float, float] + + +def _export_audio(video: VideoFileClip, temp_audio_path: str) -> None: + """Export the audio track of the given ``video`` to ``temp_audio_path``.""" + video.audio.write_audiofile(temp_audio_path, logger=None) + + +def _detect_nonsilent_parts( + audio_path: str, min_silence_len: int, silence_thresh: int +) -> List[TimeRange]: + """Return a list of non-silent time ranges from the supplied audio file.""" + sound = AudioSegment.from_file(audio_path) + nonsilent_parts = detect_nonsilent( + sound, min_silence_len=min_silence_len, silence_thresh=silence_thresh + ) + return [(start / 1000, end / 1000) for start, end in nonsilent_parts] + + +def _process_segment( + video: VideoFileClip, start: float, end: float, min_silence_len: int +) -> VideoFileClip: + """Return a processed ``VideoFileClip`` for the given time range.""" + segment_duration = end - start + segment = video.subclip(start, end) + + # Speed up short segments with FFmpeg if needed + if segment_duration >= (min_silence_len / 1000): + return segment + + with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_input, \ + tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_output: + segment.write_videofile( + temp_input.name, codec="libx264", audio_codec="aac", logger=None + ) + + ffmpeg_cmd = [ + "ffmpeg", + "-y", + "-i", + temp_input.name, + "-filter_complex", + f"[0:v]setpts={1}*PTS[v]; [0:a]atempo={1}[a]", + "-map", + "[v]", + "-map", + "[a]", + "-c:v", + "libx264", + "-c:a", + "aac", + temp_output.name, + ] + + subprocess.run(ffmpeg_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + return VideoFileClip(temp_output.name) + + +def jumpcutter( + video_path: str, + output_path: str, + silence_thresh: int = -40, + min_silence_len: int = 500, +) -> None: + """Generate a new video that skips silent segments.""" + if not os.path.isfile(video_path): + print(f"Error: The file '{video_path}' does not exist.") + return + + print(f"\nProcessing: {video_path}") + print("[1/4] Loading video...") + video = VideoFileClip(video_path) + + print("[2/4] Exporting audio for silence detection...") + temp_audio_path = "temp_audio.wav" + _export_audio(video, temp_audio_path) + + print("[3/4] Detecting non-silent audio segments...") + nonsilent_times = _detect_nonsilent_parts( + temp_audio_path, min_silence_len=min_silence_len, silence_thresh=silence_thresh + ) + + print("[4/4] Creating new video by concatenating non-silent parts...") + clips = [] + for start, end in tqdm(nonsilent_times, desc="Processing video segments"): + try: + clips.append(_process_segment(video, start, end, min_silence_len)) + except Exception as exc: # pragma: no cover - logging is side-effect only + print(f"Klaida pagreitinant segmentą {start}-{end}: {exc}. Segmentas praleidžiamas.") + continue + + final_video = concatenate_videoclips(clips) + final_video.write_videofile(output_path, codec="libx264", audio_codec="aac") diff --git a/jumpcutter/gui.py b/jumpcutter/gui.py new file mode 100644 index 0000000..5c060d5 --- /dev/null +++ b/jumpcutter/gui.py @@ -0,0 +1,118 @@ +"""Graphical user interface for Jumpcutter.""" + +import os +import tkinter as tk +from tkinter import filedialog, ttk + +from .core import jumpcutter + + +class JumpCutterApp(tk.Tk): + """Tkinter-based GUI for configuring and running Jumpcutter.""" + + def __init__(self) -> None: + super().__init__() + self.title("JumpCutter") + self.geometry("300x300") + self._create_widgets() + + # --- UI helpers ------------------------------------------------- + def _update_thresh_desc(self, value: str) -> None: + value = int(float(value)) + if value <= -55: + desc = "Labai jautru (tyla studijoje)" + elif value <= -45: + desc = "Rekomenduojama kalbiniams įrašams" + elif value <= -35: + desc = "Vidutinis slenkstis (gatvės triukšmas)" + else: + desc = "Mažas jautrumas (triukšminga aplinka)" + self.thresh_desc.config(text=f"{value} dB: {desc}") + + def _update_silence_len_desc(self, value: str) -> None: + self.silence_len_desc.config(text=f"Minimali tyla: {int(float(value))} ms") + + # --- UI setup --------------------------------------------------- + def _create_widgets(self) -> None: + main_frame = tk.Frame(self) + main_frame.pack(pady=10) + + # Silence threshold controls + thresh_frame = tk.Frame(main_frame) + thresh_frame.pack(pady=5) + + tk.Label(thresh_frame, text="Tylos slenkstis:").pack() + self.silence_thresh = tk.Scale( + thresh_frame, + from_=-60, + to=-30, + resolution=5, + orient="horizontal", + command=self._update_thresh_desc, + ) + self.silence_thresh.set(-40) + self.silence_thresh.pack() + + self.thresh_desc = tk.Label(thresh_frame, text="", fg="gray") + self.thresh_desc.pack() + self._update_thresh_desc(-40) + + # Minimum silence length controls + silence_len_frame = tk.Frame(main_frame) + silence_len_frame.pack(pady=5) + + tk.Label(silence_len_frame, text="Minimali tylos trukmė:").pack() + self.min_silence_len = tk.Scale( + silence_len_frame, + from_=100, + to=2000, + resolution=100, + orient="horizontal", + command=self._update_silence_len_desc, + ) + self.min_silence_len.set(500) + self.min_silence_len.pack() + + self.silence_len_desc = tk.Label(silence_len_frame, text="", fg="gray") + self.silence_len_desc.pack() + self._update_silence_len_desc(500) + + # Video selection button + tk.Button(self, text="Pasirinkti video", command=self._select_file).pack(pady=20) + + # Progress indicator and status label + self.progress = ttk.Progressbar(self, mode="indeterminate") + self.status_label = tk.Label(self, text="", fg="gray") + self.status_label.pack() + + # --- Actions ---------------------------------------------------- + def _select_file(self) -> None: + file_path = filedialog.askopenfilename( + title="Pasirinkite video failą", + filetypes=[("Video Files", "*.mp4 *.mkv *.webm *.mov")], + ) + if file_path: + self._process_video(file_path) + + def _process_video(self, input_path: str) -> None: + self.progress.pack(pady=10) + self.progress.start() + self.status_label.config(text="Apdorojama...", fg="green") + + try: + output_path = f"jumpcutted_{os.path.basename(input_path)}" + jumpcutter( + input_path, + output_path, + silence_thresh=int(self.silence_thresh.get()), + min_silence_len=int(self.min_silence_len.get()), + ) + self.status_label.config(text=f"Baigta! Išsaugota: {output_path}", fg="blue") + except Exception as exc: # pragma: no cover - logging is side-effect only + self.status_label.config(text=f"Klaida: {exc}", fg="red") + finally: + self.progress.stop() + self.progress.pack_forget() + + +__all__ = ["JumpCutterApp"] From 6a234fc86c24c1f396934c85b8cade692ae222f0 Mon Sep 17 00:00:00 2001 From: Alvydas Date: Sun, 2 Nov 2025 13:32:03 +0200 Subject: [PATCH 02/12] =?UTF-8?q?Lietuvinti=20Jumpcutter=20tekstus=20ir=20?= =?UTF-8?q?paai=C5=A1kinimus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jumpcutter.py | 2 +- jumpcutter/__init__.py | 2 +- jumpcutter/core.py | 33 ++++++++++++++++++++------------- jumpcutter/gui.py | 4 ++-- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/jumpcutter.py b/jumpcutter.py index 5434870..89ab039 100644 --- a/jumpcutter.py +++ b/jumpcutter.py @@ -1,4 +1,4 @@ -"""Entry point for running the Jumpcutter GUI application.""" +"""Paleidimo taškas, skirtas „Jumpcutter“ grafinės programos startui.""" from jumpcutter import JumpCutterApp diff --git a/jumpcutter/__init__.py b/jumpcutter/__init__.py index 77e4a29..eab63c3 100644 --- a/jumpcutter/__init__.py +++ b/jumpcutter/__init__.py @@ -1,4 +1,4 @@ -"""Jumpcutter package.""" +"""„Jumpcutter“ paketas.""" from .core import jumpcutter from .gui import JumpCutterApp diff --git a/jumpcutter/core.py b/jumpcutter/core.py index ebfea96..e76d899 100644 --- a/jumpcutter/core.py +++ b/jumpcutter/core.py @@ -1,4 +1,4 @@ -"""Core video processing logic for Jumpcutter.""" +"""Pagrindinė „Jumpcutter“ vaizdo apdorojimo logika.""" from __future__ import annotations @@ -17,14 +17,14 @@ def _export_audio(video: VideoFileClip, temp_audio_path: str) -> None: - """Export the audio track of the given ``video`` to ``temp_audio_path``.""" + """Išeksportuoja garso takelį iš ``video`` į laikiną ``temp_audio_path`` failą.""" video.audio.write_audiofile(temp_audio_path, logger=None) def _detect_nonsilent_parts( audio_path: str, min_silence_len: int, silence_thresh: int ) -> List[TimeRange]: - """Return a list of non-silent time ranges from the supplied audio file.""" + """Randa ir grąžina visų negarsiųjų (kalbos) atkarpų laiko intervalus.""" sound = AudioSegment.from_file(audio_path) nonsilent_parts = detect_nonsilent( sound, min_silence_len=min_silence_len, silence_thresh=silence_thresh @@ -35,11 +35,11 @@ def _detect_nonsilent_parts( def _process_segment( video: VideoFileClip, start: float, end: float, min_silence_len: int ) -> VideoFileClip: - """Return a processed ``VideoFileClip`` for the given time range.""" + """Sukuria apdorotą ``VideoFileClip`` konkrečiai laiko atkarpai.""" segment_duration = end - start segment = video.subclip(start, end) - # Speed up short segments with FFmpeg if needed + # Jei atkarpa trumpesnė nei nustatyta minimali tyla, ją išsaugome kaip yra. if segment_duration >= (min_silence_len / 1000): return segment @@ -49,6 +49,7 @@ def _process_segment( temp_input.name, codec="libx264", audio_codec="aac", logger=None ) + # Šis FFmpeg kvietimas pateikia pavyzdinį pagreitinimą, jei reikėtų korekcijų ateityje. ffmpeg_cmd = [ "ffmpeg", "-y", @@ -78,27 +79,27 @@ def jumpcutter( silence_thresh: int = -40, min_silence_len: int = 500, ) -> None: - """Generate a new video that skips silent segments.""" + """Sukuria naują video, kuriame tylos atkarpos praleidžiamos.""" if not os.path.isfile(video_path): - print(f"Error: The file '{video_path}' does not exist.") + print(f"Klaida: failas '{video_path}' nerastas.") return - print(f"\nProcessing: {video_path}") - print("[1/4] Loading video...") + print(f"\nApdorojamas failas: {video_path}") + print("[1/4] Įkeliame video failą į atmintį...") video = VideoFileClip(video_path) - print("[2/4] Exporting audio for silence detection...") + print("[2/4] Išskiriame garso takelį tylos paieškai...") temp_audio_path = "temp_audio.wav" _export_audio(video, temp_audio_path) - print("[3/4] Detecting non-silent audio segments...") + print("[3/4] Ieškome kalbos (ne tylos) atkarpų garse...") nonsilent_times = _detect_nonsilent_parts( temp_audio_path, min_silence_len=min_silence_len, silence_thresh=silence_thresh ) - print("[4/4] Creating new video by concatenating non-silent parts...") + print("[4/4] Sujungiame rastas atkarpas į naują video failą...") clips = [] - for start, end in tqdm(nonsilent_times, desc="Processing video segments"): + for start, end in tqdm(nonsilent_times, desc="Apdorojame vaizdo segmentus"): try: clips.append(_process_segment(video, start, end, min_silence_len)) except Exception as exc: # pragma: no cover - logging is side-effect only @@ -107,3 +108,9 @@ def jumpcutter( final_video = concatenate_videoclips(clips) final_video.write_videofile(output_path, codec="libx264", audio_codec="aac") + + # Tvarkome laikinus failus ir pranešame vartotojui. + if os.path.exists(temp_audio_path): + os.remove(temp_audio_path) + + print(f"Darbas baigtas! Naujas failas: {output_path}") diff --git a/jumpcutter/gui.py b/jumpcutter/gui.py index 5c060d5..05aab2e 100644 --- a/jumpcutter/gui.py +++ b/jumpcutter/gui.py @@ -1,4 +1,4 @@ -"""Graphical user interface for Jumpcutter.""" +"""Grafinė „Jumpcutter“ naudotojo sąsaja.""" import os import tkinter as tk @@ -8,7 +8,7 @@ class JumpCutterApp(tk.Tk): - """Tkinter-based GUI for configuring and running Jumpcutter.""" + """Tkinter pagrindu sukurta sąsaja „Jumpcutter“ nustatymams ir paleidimui.""" def __init__(self) -> None: super().__init__() From 0643857e4f59578697aad4cdcc0d843fd3314c64 Mon Sep 17 00:00:00 2001 From: Alvydas Date: Sun, 2 Nov 2025 13:42:12 +0200 Subject: [PATCH 03/12] Document usage in English and Lithuanian --- README.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b870c37..7c42491 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,85 @@ -GUI jumpcutter with FFMPEG. Analysis audio file for silent parts cuts it out and concatinates parts of audio + video in sync. Thus making lecture videos shorter. +# Jumpcutter GUI -Another way to improve or create fork of this project instead of cutting off silent parts, it could do 2x-10x to save some video information. To keep track +Jumpcutter is a desktop tool that trims silent segments from lecture or talk recordings to create a shorter, more engaging video. The application provides a Tkinter-based interface on top of FFmpeg, guiding you through loading a source file, choosing output options, and processing the video while keeping audio and video in sync. + +## Features +- Detects silent sections in a video by analysing the audio track and removes them automatically. +- Uses FFmpeg under the hood for reliable audio/video processing. +- Offers a simple graphical interface that explains each step of the workflow in Lithuanian. +- Keeps the codebase modular with separate `core` processing logic and `gui` orchestration modules. + +## Requirements +- Python 3.8 or later. +- FFmpeg available on your system `PATH`. +- Recommended: a virtual environment to isolate dependencies. + +## Installation +1. Clone this repository and move into the project directory: + ```bash + git clone https://github.com/your-username/jumpcutter.git + cd jumpcutter + ``` +2. (Optional) Create and activate a virtual environment. +3. Install the Python requirements: + ```bash + pip install -r requirements.txt + ``` + +## Usage +1. Ensure FFmpeg is installed and accessible from the command line. +2. Run the GUI launcher: + ```bash + python -m jumpcutter.gui + ``` +3. Follow the Lithuanian prompts in the window to select an input video, adjust silence thresholds, and start processing. +4. The processed video is saved next to the source file with silent portions removed. + +## Project Structure +- `jumpcutter/core.py` — processing pipeline that analyses audio, removes silence, and writes the final video. +- `jumpcutter/gui.py` — Tkinter interface that gathers user input and orchestrates processing with explanatory Lithuanian messages. +- `jumpcutter/__init__.py` — package metadata and convenience helpers. +- `jumpcutter.py` — legacy launcher kept for backwards compatibility. + +--- + +# Jumpcutter GUI (Lietuviškai) + +„Jumpcutter“ – tai darbalaukio įrankis, skirtas pašalinti tylias paskaitų ar pranešimų įrašų atkarpas ir taip sukurti trumpesnį, įtaigesnį vaizdo įrašą. Programa naudoja Tkinter grafinę sąsają ir FFmpeg, o lange pateikia paaiškinimus, kaip pasirinkti failą, nustatyti parametrus ir paleisti apdorojimą sinchronizuojant garsą su vaizdu. + +## Funkcijos +- Automatiškai aptinka tylias garso takelio vietas ir jas iškerpa. +- Naudoja FFmpeg, todėl apdorojimas yra patikimas ir kokybiškas. +- Pateikia paprastą grafinę sąsają su aiškiais lietuviškais paaiškinimais. +- Kodo bazė suskaidyta į atskirus `core` ir `gui` modulius, todėl lengviau prižiūrėti ir plėsti. + +## Reikalavimai +- Python 3.8 arba naujesnė versija. +- FFmpeg turi būti prieinamas per sistemos `PATH`. +- Rekomenduojama naudoti virtualią aplinką priklausomybėms atskirti. + +## Diegimas +1. Nuklonuokite šį repozitoriją ir atverkite projekto aplanką: + ```bash + git clone https://github.com/your-username/jumpcutter.git + cd jumpcutter + ``` +2. (Nebūtina) Susikurkite ir aktyvuokite virtualią aplinką. +3. Įdiekite Python priklausomybes: + ```bash + pip install -r requirements.txt + ``` + +## Naudojimas +1. Įsitikinkite, kad FFmpeg įdiegtas ir pasiekiamas per komandų eilutę. +2. Paleiskite grafinę sąsają: + ```bash + python -m jumpcutter.gui + ``` +3. Lange vadovaukitės lietuviškais nurodymais: pasirinkite įvesties vaizdo įrašą, sureguliuokite tylos slenksčius ir pradėkite apdorojimą. +4. Apdorotas vaizdo įrašas išsaugomas greta pradinio failo, pašalinus tylias atkarpas. + +## Projekto struktūra +- `jumpcutter/core.py` – apdorojimo eiga: garso analizė, tylos iškirpimas ir galutinio vaizdo įrašymas. +- `jumpcutter/gui.py` – Tkinter sąsaja, kuri surenka naudotojo nustatymus ir paleidžia apdorojimą su paaiškinimais. +- `jumpcutter/__init__.py` – paketo metainformacija ir pagalbinės funkcijos. +- `jumpcutter.py` – senesnis paleidimo scenarijus, paliktas suderinamumui. From 3499482c3e38b8febfa787f6ce8d18790563fb7b Mon Sep 17 00:00:00 2001 From: Alvydas Date: Sun, 2 Nov 2025 12:45:19 +0100 Subject: [PATCH 04/12] Update jumpcutter.py --- jumpcutter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jumpcutter.py b/jumpcutter.py index 89ab039..4527fa6 100644 --- a/jumpcutter.py +++ b/jumpcutter.py @@ -1,4 +1,4 @@ -"""Paleidimo taškas, skirtas „Jumpcutter“ grafinės programos startui.""" +"""Paleidima, skirtas „Jumpcutter“ grafinės programos startui.""" from jumpcutter import JumpCutterApp From 5fb02839c634ed212f789341ec3c040af016f5e1 Mon Sep 17 00:00:00 2001 From: Alvydas Date: Sun, 2 Nov 2025 12:45:34 +0100 Subject: [PATCH 05/12] Update jumpcutter.py --- jumpcutter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jumpcutter.py b/jumpcutter.py index 4527fa6..fb2541e 100644 --- a/jumpcutter.py +++ b/jumpcutter.py @@ -1,4 +1,4 @@ -"""Paleidima, skirtas „Jumpcutter“ grafinės programos startui.""" +"""Paleidimas, skirtas „Jumpcutter“ grafinės programos startui.""" from jumpcutter import JumpCutterApp From ad56cb6b3b74843ed62387befdc4056042ba96ea Mon Sep 17 00:00:00 2001 From: Alvydas Date: Sun, 2 Nov 2025 12:46:25 +0100 Subject: [PATCH 06/12] Update __init__.py --- jumpcutter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jumpcutter/__init__.py b/jumpcutter/__init__.py index eab63c3..0e2a4b1 100644 --- a/jumpcutter/__init__.py +++ b/jumpcutter/__init__.py @@ -1,4 +1,4 @@ -"""„Jumpcutter“ paketas.""" +"""Jumpcutter paketas.""" from .core import jumpcutter from .gui import JumpCutterApp From 0f9af872d3c70a07a38af4db90686cfc94082a91 Mon Sep 17 00:00:00 2001 From: Alvydas Date: Sun, 2 Nov 2025 12:52:04 +0100 Subject: [PATCH 07/12] Update core.py --- jumpcutter/core.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jumpcutter/core.py b/jumpcutter/core.py index e76d899..e4ccd34 100644 --- a/jumpcutter/core.py +++ b/jumpcutter/core.py @@ -17,14 +17,14 @@ def _export_audio(video: VideoFileClip, temp_audio_path: str) -> None: - """Išeksportuoja garso takelį iš ``video`` į laikiną ``temp_audio_path`` failą.""" + """Eksportuoja garso takelį iš ``video`` į laikiną ``temp_audio_path`` failą.""" video.audio.write_audiofile(temp_audio_path, logger=None) def _detect_nonsilent_parts( audio_path: str, min_silence_len: int, silence_thresh: int ) -> List[TimeRange]: - """Randa ir grąžina visų negarsiųjų (kalbos) atkarpų laiko intervalus.""" + """Randa ir grąžina visų tylos atkarpų laiko intervalus.""" sound = AudioSegment.from_file(audio_path) nonsilent_parts = detect_nonsilent( sound, min_silence_len=min_silence_len, silence_thresh=silence_thresh @@ -49,7 +49,7 @@ def _process_segment( temp_input.name, codec="libx264", audio_codec="aac", logger=None ) - # Šis FFmpeg kvietimas pateikia pavyzdinį pagreitinimą, jei reikėtų korekcijų ateityje. + # FFmpeg pagreitinimas, jei reikėtų korekcijų ateityje. ffmpeg_cmd = [ "ffmpeg", "-y", @@ -88,7 +88,7 @@ def jumpcutter( print("[1/4] Įkeliame video failą į atmintį...") video = VideoFileClip(video_path) - print("[2/4] Išskiriame garso takelį tylos paieškai...") + print("[2/4] Išskiriame garso takelį...") temp_audio_path = "temp_audio.wav" _export_audio(video, temp_audio_path) @@ -109,7 +109,7 @@ def jumpcutter( final_video = concatenate_videoclips(clips) final_video.write_videofile(output_path, codec="libx264", audio_codec="aac") - # Tvarkome laikinus failus ir pranešame vartotojui. + # Tvarkome temp failus ir pranešame vartotojui. if os.path.exists(temp_audio_path): os.remove(temp_audio_path) From 35c3c6ebe4f7c092307491f760cc1e008ca131b5 Mon Sep 17 00:00:00 2001 From: Alvydas Date: Sun, 2 Nov 2025 12:52:20 +0100 Subject: [PATCH 08/12] Update core.py --- jumpcutter/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jumpcutter/core.py b/jumpcutter/core.py index e4ccd34..3ead895 100644 --- a/jumpcutter/core.py +++ b/jumpcutter/core.py @@ -1,4 +1,4 @@ -"""Pagrindinė „Jumpcutter“ vaizdo apdorojimo logika.""" +"""Core „Jumpcutter“ vaizdo apdorojimo logika.""" from __future__ import annotations From 81a44e3f32a60367a49428642b765482374348ab Mon Sep 17 00:00:00 2001 From: Alvydas Date: Sun, 2 Nov 2025 12:56:53 +0100 Subject: [PATCH 09/12] Update gui.py --- jumpcutter/gui.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jumpcutter/gui.py b/jumpcutter/gui.py index 05aab2e..767f26c 100644 --- a/jumpcutter/gui.py +++ b/jumpcutter/gui.py @@ -37,7 +37,7 @@ def _create_widgets(self) -> None: main_frame = tk.Frame(self) main_frame.pack(pady=10) - # Silence threshold controls + # Tylos slenkscio kontrole thresh_frame = tk.Frame(main_frame) thresh_frame.pack(pady=5) @@ -57,7 +57,7 @@ def _create_widgets(self) -> None: self.thresh_desc.pack() self._update_thresh_desc(-40) - # Minimum silence length controls + # Minimali tylos trukmes kontole silence_len_frame = tk.Frame(main_frame) silence_len_frame.pack(pady=5) @@ -77,15 +77,15 @@ def _create_widgets(self) -> None: self.silence_len_desc.pack() self._update_silence_len_desc(500) - # Video selection button + # Vaizdo pasirinkimo mygtukas tk.Button(self, text="Pasirinkti video", command=self._select_file).pack(pady=20) - # Progress indicator and status label + # Progreso indikatorius ir statusas self.progress = ttk.Progressbar(self, mode="indeterminate") self.status_label = tk.Label(self, text="", fg="gray") self.status_label.pack() - # --- Actions ---------------------------------------------------- + # --- Veiksmai ---------------------------------------------------- def _select_file(self) -> None: file_path = filedialog.askopenfilename( title="Pasirinkite video failą", From a121295ca3bc199f2664e783ab36da8494b3b14a Mon Sep 17 00:00:00 2001 From: Alvydas Date: Sun, 30 Nov 2025 18:36:23 +0200 Subject: [PATCH 10/12] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c42491..23d1d92 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Jumpcutter is a desktop tool that trims silent segments from lecture or talk rec --- -# Jumpcutter GUI (Lietuviškai) +# Jumpcutter GUI Lietuviškai „Jumpcutter“ – tai darbalaukio įrankis, skirtas pašalinti tylias paskaitų ar pranešimų įrašų atkarpas ir taip sukurti trumpesnį, įtaigesnį vaizdo įrašą. Programa naudoja Tkinter grafinę sąsają ir FFmpeg, o lange pateikia paaiškinimus, kaip pasirinkti failą, nustatyti parametrus ir paleisti apdorojimą sinchronizuojant garsą su vaizdu. From 9a673ae74de26471b2792d03c40093dca1dd5bb9 Mon Sep 17 00:00:00 2001 From: Alvydas Date: Mon, 1 Dec 2025 06:30:34 +0200 Subject: [PATCH 11/12] Add files via upload --- requirements.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..504d9b2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +moviepy==1.0.3 +pydub==0.25.1 +numpy +pillow +decorator +imageio==2.9.0 +imageio-ffmpeg +proglog +opencv-python>=4.5.0 +scipy>=1.7.0 \ No newline at end of file From 84652cfa288317aca829720f54909bfb1d34b74d Mon Sep 17 00:00:00 2001 From: Alvydas Date: Mon, 1 Dec 2025 06:48:51 +0200 Subject: [PATCH 12/12] Add files via upload --- README.md | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 23d1d92..60a2fa1 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Jumpcutter GUI -Jumpcutter is a desktop tool that trims silent segments from lecture or talk recordings to create a shorter, more engaging video. The application provides a Tkinter-based interface on top of FFmpeg, guiding you through loading a source file, choosing output options, and processing the video while keeping audio and video in sync. +Jumpcutter is a desktop application that removes silent parts from lectures or talks to make a shorter and more watchable video. It offers a Tkinter interface over FFmpeg, walking you through loading a source file, selecting output options, and processing the video while maintaining audio/video sync. ## Features -- Detects silent sections in a video by analysing the audio track and removes them automatically. -- Uses FFmpeg under the hood for reliable audio/video processing. -- Offers a simple graphical interface that explains each step of the workflow in Lithuanian. -- Keeps the codebase modular with separate `core` processing logic and `gui` orchestration modules. +- Analyses the audio track to automatically detect and remove silent sections. +- Uses FFmpeg under the hood for dependable processing of audio and video. +- Provides a basic graphical interface in Lithuanian which breaks down each step in the workflow. +- Maintains modular separation between core processing logic and GUI orchestration modules. ## Requirements - Python 3.8 or later. @@ -31,31 +31,32 @@ Jumpcutter is a desktop tool that trims silent segments from lecture or talk rec ```bash python -m jumpcutter.gui ``` -3. Follow the Lithuanian prompts in the window to select an input video, adjust silence thresholds, and start processing. -4. The processed video is saved next to the source file with silent portions removed. +3. Select an input video, set thresholds for silence, and begin processing by following the Lithuanian instructions in the GUI. +4. The output video will be saved in the same location as the source file with silences removed. + ## Project Structure -- `jumpcutter/core.py` — processing pipeline that analyses audio, removes silence, and writes the final video. -- `jumpcutter/gui.py` — Tkinter interface that gathers user input and orchestrates processing with explanatory Lithuanian messages. -- `jumpcutter/__init__.py` — package metadata and convenience helpers. -- `jumpcutter.py` — legacy launcher kept for backwards compatibility. +- `jumpcutter/core.py` — Contains a processing pipeline which detects silences in audio, removes them and writes final output video. +- `jumpcutter/gui.py` — A Tkinter based interface to collect inputs from userand manage processing.Displaying all messages to user in Lithuanian. +- `jumpcutter/__init__.py` — Contains package meta data and some utility functions. +- `jumpcutter.py` — older launcher still around for keeping things compatible with older versions. --- # Jumpcutter GUI Lietuviškai -„Jumpcutter“ – tai darbalaukio įrankis, skirtas pašalinti tylias paskaitų ar pranešimų įrašų atkarpas ir taip sukurti trumpesnį, įtaigesnį vaizdo įrašą. Programa naudoja Tkinter grafinę sąsają ir FFmpeg, o lange pateikia paaiškinimus, kaip pasirinkti failą, nustatyti parametrus ir paleisti apdorojimą sinchronizuojant garsą su vaizdu. +„Jumpcutter“ yra darbalaukio programa, skirta tylos fragmentams pašalinti iš paskaitų ar pristatymų, kad vaizdo įrašai būtų trumpesni ir įtraukiantys. Ji veikia su „Tkinter“ grafine sąsaja, naudodama „FFmpeg“, ir savo lange paaiškina, kaip pasirinkti failą, nustatyti parametrus ir pradėti apdorojimą sinchronizuojant garsą su vaizdo įrašu. ## Funkcijos -- Automatiškai aptinka tylias garso takelio vietas ir jas iškerpa. -- Naudoja FFmpeg, todėl apdorojimas yra patikimas ir kokybiškas. -- Pateikia paprastą grafinę sąsają su aiškiais lietuviškais paaiškinimais. -- Kodo bazė suskaidyta į atskirus `core` ir `gui` modulius, todėl lengviau prižiūrėti ir plėsti. +- Automatiškai aptinka ir iškirpa tylias garso takelio dalis. +- Naudoja „FFmpeg“, kad apdorojimas būtų patikimas ir aukštos kokybės. +- Turi paprastą grafinę sąsają aiškia lietuvių kalba. +- Šaltinio kodas yra padalintas į atskirus pagrindinius ir gui modulius, kad būtų lengviau prižiūrėti ir išplėsti. -## Reikalavimai -- Python 3.8 arba naujesnė versija. -- FFmpeg turi būti prieinamas per sistemos `PATH`. -- Rekomenduojama naudoti virtualią aplinką priklausomybėms atskirti. +## REIKALAVIMAI +- Python 3.8 (ar naujesnis). +- FFmpeg prienamas sistemos PATH. +-Rekomenduoti naudoti virtualią aplinką priklausomybėms atskirti. ## Diegimas 1. Nuklonuokite šį repozitoriją ir atverkite projekto aplanką: @@ -70,13 +71,13 @@ Jumpcutter is a desktop tool that trims silent segments from lecture or talk rec ``` ## Naudojimas -1. Įsitikinkite, kad FFmpeg įdiegtas ir pasiekiamas per komandų eilutę. +1. Įsitikinkite, kad FFmpeg yra įdiegtas ir pasiekiamas iš komandinės eilutės. 2. Paleiskite grafinę sąsają: ```bash python -m jumpcutter.gui ``` 3. Lange vadovaukitės lietuviškais nurodymais: pasirinkite įvesties vaizdo įrašą, sureguliuokite tylos slenksčius ir pradėkite apdorojimą. -4. Apdorotas vaizdo įrašas išsaugomas greta pradinio failo, pašalinus tylias atkarpas. +4. Apdorotas išvesties vaizdo įrašas bus išsaugotas šalia originalaus failo, pašalinus tylias dalis. ## Projekto struktūra - `jumpcutter/core.py` – apdorojimo eiga: garso analizė, tylos iškirpimas ir galutinio vaizdo įrašymas.