Created
January 15, 2026 13:52
-
-
Save thegamecracks/990c88c40e7393f7800b310f8d167599 to your computer and use it in GitHub Desktop.
Calculate video bitrate to achieve a given filesize and duration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """Calculate the video bitrate for video compression software | |
| to achieve a given filesize and duration. | |
| Intended for use with HandBrake: https://handbrake.fr/ | |
| Result bitrate should be copied to your constant/average bitrate. | |
| Note that input filesize is in megabytes, not mebibytes. | |
| """ | |
| import re | |
| import sys | |
| from tkinter import Misc, StringVar, Tk | |
| from tkinter.ttk import Entry, Frame, Label, LabelFrame, Spinbox | |
| def main() -> None: | |
| def refresh_output() -> None: | |
| try: | |
| filesize = float(video_filesize.var.get()) * 8000 | |
| duration = parse_video_duration(video_length.var.get()) | |
| audio = float(audio_bitrate.var.get()) | |
| except ValueError: | |
| return output_bitrate.var.set("") | |
| output_bitrate_num = (filesize - audio * duration) / duration | |
| if output_bitrate_num <= 0: | |
| return output_bitrate.var.set("Video too long") | |
| output_bitrate.var.set(str(int(output_bitrate_num))) | |
| enable_windows_dpi_awareness() | |
| app = Tk() | |
| app.title("HandBrake Video Bitrate Calculator") | |
| app.geometry("854x200") | |
| content = Frame(app, padding=10) | |
| content.pack(expand=True, fill="both") | |
| content.grid_columnconfigure(0, weight=3) | |
| content.grid_columnconfigure(1, weight=1) | |
| content.grid_rowconfigure(0, weight=1) | |
| inputs = Frame(content) | |
| inputs.grid(row=0, column=0, sticky="nesw") | |
| inputs.grid_columnconfigure(0, weight=1) | |
| inputs.grid_rowconfigure(0, weight=3) | |
| inputs.grid_rowconfigure(1, weight=1) | |
| outputs = Frame(content) | |
| outputs.grid(row=0, column=1, sticky="nesw") | |
| outputs.grid_anchor("center") | |
| video = LabelFrame(inputs, text="Video", padding=10) | |
| video.grid(row=0, column=0, sticky="nesw") | |
| video.grid_anchor("center") | |
| video.grid_columnconfigure(0, weight=1) | |
| video_filesize = LabelSpinbox(video, label="Desired filesize (MB):", value="50") | |
| video_filesize.grid(sticky="ew") | |
| video_length = LabelEntry(video, label="Length (mm:ss):", value="3:00") | |
| video_length.grid(sticky="ew") | |
| audio = LabelFrame(inputs, text="Audio", padding=10) | |
| audio.grid(row=1, column=0, sticky="nesw") | |
| audio.grid_anchor("center") | |
| audio.grid_columnconfigure(0, weight=1) | |
| audio_bitrate = LabelSpinbox(audio, label="Bitrate (kbps):", value="96") | |
| audio_bitrate.grid(sticky="ew") | |
| output_bitrate = LabelEntry(outputs, label="Result bitrate (kbps):") | |
| output_bitrate.grid(sticky="ew") | |
| video_filesize.var.trace_add("write", lambda *_: refresh_output()) | |
| video_length.var.trace_add("write", lambda *_: refresh_output()) | |
| audio_bitrate.var.trace_add("write", lambda *_: refresh_output()) | |
| refresh_output() | |
| app.mainloop() | |
| def parse_video_duration(s: str) -> int: | |
| s = s.strip() | |
| m = re.match(r"(?:(\d+):)?(?:(\d+):)?(\d+)", s) | |
| if m is None: | |
| raise ValueError(f"Invalid duration: {s}") | |
| hours = int(m[1] or 0) | |
| minutes = int(m[2] or 0) | |
| seconds = int(m[3] or 0) | |
| if m[1] is not None and m[2] is None: | |
| hours, minutes = 0, hours | |
| return hours * 3600 + minutes * 60 + seconds | |
| def enable_windows_dpi_awareness() -> None: | |
| if sys.platform == "win32": | |
| from ctypes import windll | |
| windll.shcore.SetProcessDpiAwareness(2) | |
| class LabelEntry(Frame): | |
| def __init__(self, parent: Misc, *, label: str, value: str = "") -> None: | |
| super().__init__(parent) | |
| self.parent = parent | |
| self.grid_columnconfigure(1, weight=1) | |
| self.label = Label(self, text=label) | |
| self.label.grid(row=0, column=0, sticky="w") | |
| self.var = StringVar(self, value=value) | |
| self.entry = Entry(self, textvariable=self.var) | |
| self.entry.grid(row=0, column=1, sticky="ew", padx=(5, 0)) | |
| class LabelSpinbox(Frame): | |
| def __init__(self, parent: Misc, *, label: str, value: str = "", **kwargs) -> None: | |
| super().__init__(parent) | |
| self.parent = parent | |
| self.grid_columnconfigure(1, weight=1) | |
| self.label = Label(self, text=label) | |
| self.label.grid(row=0, column=0, sticky="w") | |
| self.var = StringVar(self, value=value) | |
| self.entry = Spinbox(self, from_=0, to=9999, textvariable=self.var) | |
| self.entry.grid(row=0, column=1, sticky="ew", padx=(5, 0)) | |
| if __name__ == "__main__": | |
| main() |
Author
thegamecracks
commented
Jan 15, 2026
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment