Skip to content

Commit

Permalink
MVP
Browse files Browse the repository at this point in the history
  • Loading branch information
patillacode committed Nov 18, 2023
1 parent e1302be commit 710a930
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 28 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ cover/
.cache/
.pytest_cache/
*.log
.vscode/
todo.md
26 changes: 16 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,25 @@ This project includes a script (`space_saver.py`) that converts video files to t

1. Clone this repository.
2. Navigate to the project directory.
3. Run `make setup` to set up the project.
3. Run `make install` to set up the project.

## Usage

Run `make run` to execute the script.

You can also run the script directly with `python space_saver.py -p <path> -f <format> [-d] [-q] [-c <crf>]`, where:

- `<path>` is the path to the directory containing the files to convert.
- `<format>` is the file format to convert.
- `-d` is an optional flag for a dry run without actually converting files.
- `-q` is an optional flag to keep the ffmpeg output to a minimum while converting.
- `<crf>` is an optional argument to set the Constant Rate Factor (CRF) value for the ffmpeg command (default is 23).
```bash
usage: space_saver.py [-h] -p PATH -f FORMAT [-d] [-q] [-c CRF]

Convert files to .mp4 with H.265 codec

options:
-h, --help show this help message and exit
-p PATH, --path PATH Path to the directory containing the .\{format\} files to convert
into .mp4 files with H.265 codec
-f FORMAT, --format FORMAT
File formats to convert, if not given all files will be checked
-d, --dry-run Perform a dry run without actually converting files
-q, --quiet Keep the ffmpeg output to a minimum while converting
-c CRF, --crf CRF Set the Constant Rate Factor (CRF) value for the ffmpeg command
```
## Project Structure
Expand Down
47 changes: 47 additions & 0 deletions ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# https://beta.ruff.rs/docs/configuration/#using-rufftoml

select = ["F", "E", "W", "I001"]
line-length = 90
show-fixes = false
target-version = "py311"
task-tags = ["TODO", "FIXME"]
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".hg",
".mypy_cache",
".nox",
".pants.d",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"venv",
]

[isort]
known-first-party = []
known-third-party = []
section-order = [
"future",
"standard-library",
"django",
"third-party",
"first-party",
"local-folder",
]
combine-as-imports = true
split-on-trailing-comma = false
lines-between-types = 1

# [isort.sections]
# "django" = ["django"]
114 changes: 96 additions & 18 deletions space_saver.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,35 @@
"""
This script converts video files to .mp4 format with H.265 codec.
It walks through a directory and its subdirectories looking for files with a given
extension (or all files if no extension is given), and converts them to .mp4 with
H.265 codec.
The original file is deleted if the conversion is successful,
and the new file is given permissions of 755.
The script also calculates the space saved by the conversion and prints it at the end.
"""

import argparse
import math
import os
import sys
import time

import ffmpeg

from termcolor import colored


def convert_size(size_bytes):
"""
Converts the given size in bytes to a human-readable format.
Args:
size_bytes (int): The size in bytes.
Returns:
str: The size in a human-readable format.
"""
if size_bytes == 0:
return "0B"
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
Expand All @@ -16,7 +39,20 @@ def convert_size(size_bytes):
return f"{s} {size_name[i]}"


def convert_single_file(input_file_path, mp4_file_path, quiet, crf):
def convert_single_file(input_file_path, mp4_file_path, quiet, crf, extension):
"""
Converts a single video file to .mp4 format with H.265 codec.
Args:
input_file_path (str): The path to the input file.
mp4_file_path (str): The path to the output .mp4 file.
quiet (bool): Whether to keep the ffmpeg output to a minimum while converting.
crf (int): The Constant Rate Factor (CRF) value for the ffmpeg command.
extension (str): The extension of the input file.
Returns:
int: The space saved by the conversion, in bytes.
"""
original_size = os.path.getsize(input_file_path)
probe = ffmpeg.probe(input_file_path)
video_stream = next(
Expand All @@ -29,19 +65,24 @@ def convert_single_file(input_file_path, mp4_file_path, quiet, crf):
print(f"{input_file_path} is already using H.265 codec, skipping conversion")
return
try:
print(f"Converting {colored(input_file_path, 'yellow')} to mp4 with H.265 codec")
print(
f"Converting {colored(input_file_path, 'yellow')} to mp4 with H.265 codec"
)
start_time = time.time()
ffmpeg.input(input_file_path).output(
mp4_file_path,
vcodec="libx265",
crf=str(crf),
acodec="aac",
strict="experimental",
).overwrite_output().run(quiet=quiet)
end_time = time.time()
except ffmpeg.Error as err:
print(
f"Error occurred while converting {colored(input_file_path, 'yellow')} to "
f"mp4: {colored(err, 'red')}"
)
sys.exit()
else:
# If the conversion was successful, delete the original file and change
# permissions of the new file
Expand All @@ -50,53 +91,87 @@ def convert_single_file(input_file_path, mp4_file_path, quiet, crf):
new_size = os.path.getsize(mp4_file_path)
os.chmod(mp4_file_path, 0o755)
space_saved = original_size - new_size
print_file_sizes(original_size, new_size)
print_file_sizes(original_size, new_size, extension)
hours, rem = divmod(end_time - start_time, 3600)
minutes, seconds = divmod(rem, 60)
formatted_time = "{:0>2}:{:0>2}:{:05.2f}".format(
int(hours), int(minutes), seconds
)
print(f"Time elapsed: {colored(formatted_time, 'white')}")
return space_saved


def print_file_sizes(original_size, new_size):
def print_file_sizes(original_size, new_size, extension):
"""
Prints the original and new file sizes, and the space saved by the conversion.
Args:
original_size (int): The size of the original file, in bytes.
new_size (int): The size of the new file, in bytes.
extension (str): The extension of the original file.
"""
size_reduction = ((original_size - new_size) / original_size) * 100
print(
f"Original mkv file size: {colored(convert_size(original_size), 'magenta')}\n"
f"New mp4 file size: {colored(convert_size(new_size), 'orange')}\n"
f"Size reduction: {colored(int(size_reduction), 'orange')}%"
f"Original {extension} file size: "
f"{colored(convert_size(original_size), 'magenta')}\n"
f"New mp4 file size: {colored(convert_size(new_size), 'cyan')}\n"
f"Size reduction: {colored(int(size_reduction), 'cyan')}%"
)


def convert_to_H265_codec(path, format, dry_run, crf, quiet=False):
def convert_to_H265_codec(path, extension, dry_run, crf, quiet=False):
"""
Converts all video files in the given directory and its subdirectories to .mp4 format
with H.265 codec.
Args:
path (str): The path to the directory containing the video files to convert.
extension (str): The extension of the video files to convert, or None to convert
all files.
dry_run (bool): Whether to perform a dry run without actually converting files.
crf (int): The Constant Rate Factor (CRF) value for the ffmpeg command.
quiet (bool): Whether to keep the ffmpeg output to a minimum while converting.
"""
total_space_saved = 0
for root, _, files in os.walk(path):
for file in files:
print("Looking at file: ", file)
if file.endswith(f".{format}") or file.endswith(f".{format.upper()}"):
if (
extension is None
or file.endswith(f".{extension}")
or file.endswith(f".{extension.upper()}")
):
file_path = os.path.join(root, file)
mp4_file_path = os.path.splitext(file_path)[0] + ".mp4"
base_file_path = os.path.splitext(file_path)[0]
mp4_file_path = f"{base_file_path}_H265.mp4"
if dry_run:
print(f"Would convert {file_path} to {mp4_file_path}")
else:
space_saved = convert_single_file(
file_path, mp4_file_path, quiet, crf
file_path, mp4_file_path, quiet, crf, extension
)
total_space_saved += space_saved if space_saved else 0
print(colored(f"Total space saved: {convert_size(total_space_saved)}", "yellow"))


if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Convert files to .mp4 with H.265 codec")
def parse_arguments():
parser = argparse.ArgumentParser(
description="Convert files to .mp4 with H.265 codec"
)
parser.add_argument(
"-p",
"--path",
required=True,
help=(
"Path to the directory containing the .\{format\} files to convert into "
r"Path to the directory containing the .\{format\} files to convert into "
".mp4 files with H.265 codec"
),
)
parser.add_argument(
"-f",
"--format",
required=True,
help="File formats to convert",
required=False,
help="File formats to convert, if not given all files will be checked",
)
parser.add_argument(
"-d",
Expand All @@ -115,7 +190,10 @@ def convert_to_H265_codec(path, format, dry_run, crf, quiet=False):
"--crf",
type=int,
default=23,
help="Set the Constant Rate Factor (CRF) value for the ffmpeg command",
)
args = parser.parse_args()
return parser.parse_args()


if __name__ == "__main__":
args = parse_arguments()
convert_to_H265_codec(args.path, args.format, args.dry_run, args.crf, args.quiet)

0 comments on commit 710a930

Please sign in to comment.