Рефакторинг в питон код

This commit is contained in:
2024-12-23 18:01:21 +03:00
parent fc8aefab83
commit 581530c6ec
7 changed files with 215 additions and 174 deletions

25
.gitignore vendored
View File

@ -1,2 +1,23 @@
input/*
output/*
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
# UV
uv.lock
# Environment variables
.env
# Ruff
.ruff_cache
# Project
input
output

35
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,35 @@
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v3.19.0
hooks:
- id: pyupgrade
args: [--py313-plus]
- repo: https://github.com/crate-ci/typos
rev: v1.28.1
hooks:
- id: typos
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.1
hooks:
- id: ruff
args: [ --fix ]
- id: ruff-format
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: check-docstring-first
- id: check-added-large-files
- id: check-yaml
- id: debug-statements
- id: check-merge-conflict
- id: double-quote-string-fixer
- id: end-of-file-fixer
- repo: meta
hooks:
- id: check-hooks-apply
- id: check-useless-excludes

108
main.py Normal file
View File

@ -0,0 +1,108 @@
from logging import getLogger
from pathlib import Path
import ffmpeg
from ffmpeg.nodes import FilterableStream
from rich.prompt import Prompt
from utils.log import configure_logging
PATH = Path(__file__).parent
INPUT_DIR = PATH / 'input'
OUTPUT_DIR = PATH / 'output'
OUTPUT_ARGS = {'vcodec': 'libvpx-vp9', 'pix_fmt': 'yuva420p', 'loglevel': 'error', 'y': None}
logger = getLogger(__name__)
def compress(input_file: Path, output_file: Path, sticker_width: int, bitrate: int | None = None):
file_input = ffmpeg.input(input_file)
file_output = OUTPUT_DIR / input_file.name.replace(input_file.suffix, '.webm')
if not isinstance(file_input, FilterableStream):
logger.error(f'Failed to process file: {input_file}')
raise Exception('Failed to process file')
stream = ffmpeg.filter(file_input, 'fps', fps=25, round='up')
stream = ffmpeg.filter(
stream, 'scale', sticker_width, -1, force_original_aspect_ratio='decrease'
)
stream = ffmpeg.filter(
stream, 'pad', sticker_width, sticker_width, '(ow-iw)/2', '(oh-ih)/2', color='0x00000000'
)
if input_file.suffix == '.gif':
duration = float(ffmpeg.probe(input_file)['streams'][0]['duration'])
speed_factor = 1
if duration > 3:
speed_factor = duration / 3
stream = ffmpeg.filter(stream, 'setpts', f'PTS/{speed_factor}')
args = OUTPUT_ARGS
if bitrate:
args['video_bitrate'] = f'{bitrate}k'
output = ffmpeg.output(stream, filename=file_output, **args)
ffmpeg.run(output)
file_size = file_output.stat().st_size
if file_size / 1024 > 256:
if not bitrate:
bitrate = int(int(ffmpeg.probe(output_file)['format']['bit_rate']) / 1000)
bitrate_offset = 100
if file_size / 256 * 1024 > 2:
bitrate_offset = 100 * (file_size / (256 * 1024))
bitrate = int(bitrate - bitrate_offset)
logger.info(
f'File size is too high [{int(file_size / 1024)} KB], reducing to {bitrate}k...'
)
return compress(input_file, output_file, sticker_width, bitrate)
def main():
if not INPUT_DIR.exists():
INPUT_DIR.mkdir(parents=True)
if not OUTPUT_DIR.exists():
OUTPUT_DIR.mkdir(parents=True)
for file in OUTPUT_DIR.iterdir():
file.unlink()
sticker_type = Prompt.ask('Do you want to create stickers or emoji?', choices=['s', 'e'])
match sticker_type:
case 's':
STICKER_WIDTH = 512
case 'e':
STICKER_WIDTH = 100
case _:
STICKER_WIDTH = 512
for file in INPUT_DIR.iterdir():
logger.info(f'Processing {file.name}...')
if file.suffix not in ('.png', '.jpg', '.webp', '.gif'):
logger.warning(f'Unsupported file type: {file.suffix}')
continue
file_path = INPUT_DIR / file.name
file_output = OUTPUT_DIR / file.name.replace(file.suffix, '.webm')
compress(file_path, file_output, STICKER_WIDTH)
if __name__ == '__main__':
configure_logging()
main()

34
pyproject.toml Normal file
View File

@ -0,0 +1,34 @@
[project]
name = "telegramstickersgenerator-git"
version = "1.0.0"
description = "A simple tool to convert any images and/or gifs to webm extension so that they can be used to create animated sticker/emoji packs in Telegram."
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"ffmpeg-python==0.2.0",
"rich==13.9.4",
]
[dependency-groups]
dev = [
"poethepoet==0.31.1",
"pre-commit==4.0.1",
]
[tool.poe.tasks]
_git = "git add ."
_lint = "pre-commit run --all-files"
lint = ["_git", "_lint"]
[tool.ruff]
target-version = "py313"
line-length = 100
[tool.ruff.lint]
extend-select = ["F", "UP", "B", "SIM", "I"]
[tool.ruff.format]
quote-style = "single"
indent-style = "space"
docstring-code-format = true

View File

@ -1,172 +0,0 @@
# Variables
$ffmpegPath = (Get-Command ffmpeg -ErrorAction SilentlyContinue).Path
$ffprobePath = (Get-Command ffprobe -ErrorAction SilentlyContinue).Path
$inputDir = "./input"
$outputDir = "./output"
# Check if ffmpeg and ffprobe executables exist
if (-not $ffmpegPath -or -not $ffprobePath) {
Write-Host "ffmpeg or ffprobe is not installed on this system. Exiting..."
exit 1
}
# Ask user if he wants to do stickers or emoji
$choice = Read-Host "Do you want to create stickers or emoji? (s/e)"
if ($choice -eq "s") {
$stickerWidth = 512
} elseif ($choice -eq "e") {
$stickerWidth = 100
} else {
Write-Host "Invalid choice. Exiting..."
exit 1
}
# Check if the output directory exists, if not, create it
if (-not (Test-Path $outputDir)) {
New-Item -Path $outputDir -ItemType Directory -Force | Out-Null
}
# Functions
# Function to get the bitrate of a video file using ffprobe
function Get-Bitrate {
param (
[string]$filePath
)
# Get bitrate from ffprobe
$bitrateOutput = & $ffprobePath -v error -select_streams v:0 -show_entries stream=bit_rate -of default=noprint_wrappers=1:nokey=1 $filePath
if ([string]::IsNullOrEmpty($bitrateOutput) -or $bitrateOutput -eq "N/A") {
# If direct bitrate is not available, calculate bitrate based on file size and duration
$fileSize = (Get-Item $filePath).Length
$durationOutput = & $ffprobePath -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 $filePath
if ($fileSize -gt 0 -and $durationOutput -ne "N/A" -and $durationOutput -ne "") {
$duration = [float]$durationOutput
$bitrate = [math]::Round(($fileSize * 8) / $duration / 1000) # in kbps
return $bitrate
} else {
Write-Host "Unable to determine bitrate for $filePath."
return 1000
}
}
return [int]$bitrateOutput
}
# Function to get the duration of a video file using ffprobe
function Get-Duration {
param (
[string]$filePath
)
$durationOutput = & $ffprobePath -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 $filePath
if ($durationOutput -ne "N/A" -and $durationOutput -ne "") {
return [float]$durationOutput
} else {
Write-Host "Unable to determine duration for $filePath."
return $null
}
}
# Function to compress the video file until it is below the specified size
function Compress-File {
param (
[string]$inputFilePath,
[string]$outputFilePath,
[int]$initialBitrate,
[int]$maxSizeKB
)
$bitrate = $initialBitrate
$originalDuration = Get-Duration -filePath $file.FullName
$speedFactor = $originalDuration / 3
$outputFilePathTemp = "$outputFilePath.webm"
if ($speedFactor -le 1) {
$speedFactor = 1
}
do {
Write-Host Trying bitrate: ${bitrate}k
# Compress with the current bitrate
& $ffmpegPath -loglevel quiet -i $inputFilePath -t 3 -r 25 -filter:v "setpts=PTS/$speedFactor,scale=-1:$stickerWidth" -c:v libvpx-vp9 -b:v ${bitrate}k $outputFilePathTemp -y
# Check the size of the compressed file
$fileSize = (Get-Item $outputFilePathTemp).Length
$fileSizeKB = [math]::Round($fileSize / 1KB)
if ($fileSizeKB -le $maxSizeKB) {
# Rename the temporary file to the final output file name
if (Test-Path $outputFilePath) { Remove-Item -Path $outputFilePath -Force }
Rename-Item -Path $outputFilePathTemp -NewName (Split-Path $outputFilePath -Leaf)
return
}
$bitrateOffset = 100
if (($fileSizeKB / $maxSizeKB) -gt 2) {
$bitrateOffset = 100 * ($fileSizeKB / $maxSizeKB)
}
# Reduce the bitrate
$bitrate = [math]::Max([int]($bitrate - $bitrateOffset), 100) # Ensuring bitrate doesn't go below 100k
} while ($bitrate -gt 100)
# If bitrate falls too low, retain the last generated file
if (Test-Path $outputFilePathTemp) {
if (Test-Path $outputFilePath) { Remove-Item -Path $outputFilePath -Force }
Rename-Item -Path $outputFilePathTemp -NewName (Split-Path $outputFilePath -Leaf)
}
}
# Process each file in the input directory
foreach ($file in Get-ChildItem -Path $inputDir -File) {
$outputFile = "$outputDir/$($file.BaseName).webm"
# Check if the file is a PNG or GIF
if ($file.Extension -notin ".png", ".gif") {
continue
}
Write-Host "Processing $($file.FullName)..."
# Convert the file to WebM format
if ($file.Extension -eq ".png") {
& $ffmpegPath -loglevel quiet -i $file.FullName -r 25 -vf "scale=-1:$stickerWidth" -c:v libvpx-vp9 -pix_fmt rgba $outputFile -y
}
if ($file.Extension -eq ".gif") {
# Get the original duration of the GIF
$originalDuration = Get-Duration -filePath $file.FullName
if ($originalDuration -gt 0) {
$speedFactor = $originalDuration / 3
if ($speedFactor -le 1) {
$speedFactor = 1
}
# Convert the file to WebM format
& $ffmpegPath -loglevel quiet -i $file.FullName -t 3 -r 25 -filter:v "setpts=PTS/$speedFactor,scale=-1:$stickerWidth" -c:v libvpx-vp9 $outputFile -y
} else {
Write-Host "Invalid duration for GIF $($file.FullName). Skipping..."
continue
}
# Check if the file size exceeds 256 KB (262144 bytes)
$fileSize = (Get-Item $outputFile).Length
if ($fileSize -gt 255 * 1024) {
Write-Host "File $($outputFile) exceeds 256 KB, compressing..."
# Get the initial bitrate of the WebM file
$initialBitrate = Get-Bitrate -filePath $outputFile
# Compress the file until it is below 255 KB
Compress-File -inputFilePath $file.FullName -outputFilePath $outputFile -initialBitrate $initialBitrate -maxSizeKB 255
}
}
}

0
utils/__init__.py Normal file
View File

15
utils/log.py Normal file
View File

@ -0,0 +1,15 @@
import logging
from rich.logging import RichHandler
def configure_logging():
DATE_FORMAT = '[%d.%m %H:%M:%S]'
LOGGER_FORMAT = '%(asctime)s %(message)s'
logging.basicConfig(
level=logging.INFO,
format=LOGGER_FORMAT,
datefmt=DATE_FORMAT,
handlers=[RichHandler(show_time=False, rich_tracebacks=True)],
)