nodex.pw-convertor/converter.py
2026-06-04 13:41:12 +05:00

210 lines
7.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import subprocess
import shutil
import tempfile
from pathlib import Path
IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".bmp", ".gif", ".tiff", ".tif", ".ico", ".avif", ".heic", ".heif"}
VIDEO_EXTS = {".mp4", ".avi", ".mov", ".mkv", ".webm", ".flv", ".wmv", ".m4v", ".3gp", ".ts", ".mts", ".mpeg", ".mpg"}
AUDIO_EXTS = {".mp3", ".wav", ".flac", ".ogg", ".m4a", ".opus", ".aac", ".wma", ".aiff", ".alac"}
ARCHIVE_EXTS = {".zip", ".tar", ".gz", ".bz2", ".7z", ".rar", ".xz"}
IMAGE_TARGETS = ["png", "jpg", "webp", "bmp", "gif", "tiff", "ico"]
VIDEO_TARGETS = ["mp4", "avi", "mov", "mkv", "webm", "gif"]
VIDEO_TO_AUDIO = ["mp3", "wav", "flac", "aac", "opus"]
AUDIO_TARGETS = ["mp3", "wav", "flac", "ogg", "m4a", "opus", "aac"]
ARCHIVE_TARGETS = ["zip", "tar.gz", "tar.bz2", "7z"]
def detect_type(path: Path) -> str:
name = path.name.lower()
# двойные суффиксы — tar.gz и т.д.
if name.endswith((".tar.gz", ".tar.bz2", ".tar.xz", ".tgz", ".tbz2")):
return "archive"
suf = path.suffix.lower()
if suf in IMAGE_EXTS:
return "image"
if suf in VIDEO_EXTS:
return "video"
if suf in AUDIO_EXTS:
return "audio"
if suf in ARCHIVE_EXTS:
return "archive"
return "unknown"
def get_targets(file_type: str) -> list:
if file_type == "image":
return IMAGE_TARGETS
if file_type == "video":
return VIDEO_TARGETS + VIDEO_TO_AUDIO
if file_type == "audio":
return AUDIO_TARGETS
if file_type == "archive":
return ARCHIVE_TARGETS
return []
def _convert_image(src: Path, dst: Path) -> tuple[bool, str]:
try:
from PIL import Image
img = Image.open(src)
ext = dst.suffix.lower()
# jpeg не понимает прозрачность — заливаем белым
if ext in (".jpg", ".jpeg"):
if img.mode in ("RGBA", "LA", "P"):
bg = Image.new("RGB", img.size, (255, 255, 255))
if img.mode == "P":
img = img.convert("RGBA")
mask = img.split()[-1] if img.mode in ("RGBA", "LA") else None
bg.paste(img, mask=mask)
img = bg
elif img.mode != "RGB":
img = img.convert("RGB")
if ext == ".ico":
img.save(dst, sizes=[(16, 16), (32, 32), (48, 48), (64, 64), (128, 128)])
return True, ""
kwargs = {}
if ext in (".jpg", ".jpeg"):
kwargs["quality"] = 95
elif ext == ".webp":
kwargs["quality"] = 90
img.save(dst, **kwargs)
return True, ""
except Exception as e:
return False, str(e)
def _convert_ffmpeg(src: Path, dst: Path, ffmpeg_bin: str) -> tuple[bool, str]:
ext = dst.suffix.lower()
# gif из видео нормальный — с палитрой
if ext == ".gif":
cmd = [
ffmpeg_bin, "-i", str(src), "-y",
"-vf", "fps=12,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse",
str(dst),
]
else:
cmd = [ffmpeg_bin, "-i", str(src), "-y"]
if ext == ".mp3":
cmd += ["-codec:a", "libmp3lame", "-qscale:a", "2", "-vn"]
elif ext == ".flac":
cmd += ["-codec:a", "flac", "-vn"]
elif ext == ".ogg":
cmd += ["-codec:a", "libvorbis", "-qscale:a", "6", "-vn"]
elif ext == ".opus":
cmd += ["-codec:a", "libopus", "-b:a", "192k", "-vn"]
elif ext == ".aac":
cmd += ["-codec:a", "aac", "-b:a", "256k", "-vn"]
elif ext == ".wav":
cmd += ["-codec:a", "pcm_s16le", "-vn"]
elif ext == ".m4a":
cmd += ["-codec:a", "aac", "-b:a", "256k", "-vn"]
cmd.append(str(dst))
try:
r = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
if r.returncode != 0:
return False, r.stderr[-1000:]
return True, ""
except subprocess.TimeoutExpired:
return False, "таймаут конвертации"
except FileNotFoundError:
return False, "ffmpeg не найден — запусти setup-ffmpeg или поставь через пакетный менеджер"
except Exception as e:
return False, str(e)
def _convert_archive(src: Path, dst: Path) -> tuple[bool, str]:
import zipfile
import tarfile
tmp = Path(tempfile.mkdtemp())
try:
src_name = src.name.lower()
# распаковываем что пришло
if src_name.endswith(".zip"):
with zipfile.ZipFile(src) as z:
z.extractall(tmp)
elif src_name.endswith((".tar.gz", ".tgz")):
with tarfile.open(src, "r:gz") as t:
t.extractall(tmp)
elif src_name.endswith((".tar.bz2", ".tbz2")):
with tarfile.open(src, "r:bz2") as t:
t.extractall(tmp)
elif src_name.endswith((".tar.xz", ".txz")):
with tarfile.open(src, "r:xz") as t:
t.extractall(tmp)
elif src_name.endswith(".tar"):
with tarfile.open(src, "r:") as t:
t.extractall(tmp)
elif src_name.endswith(".7z"):
import py7zr
with py7zr.SevenZipFile(src, mode="r") as z:
z.extractall(tmp)
elif src_name.endswith(".rar"):
import rarfile
with rarfile.RarFile(src) as r:
r.extractall(tmp)
else:
return False, "неизвестный формат архива"
# пакуем в нужное
dst_name = dst.name.lower()
if dst_name.endswith(".zip"):
with zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as z:
for f in tmp.rglob("*"):
if f.is_file():
z.write(f, f.relative_to(tmp))
elif dst_name.endswith(".tar.gz"):
with tarfile.open(dst, "w:gz") as t:
for f in tmp.rglob("*"):
if f.is_file():
t.add(f, arcname=str(f.relative_to(tmp)))
elif dst_name.endswith(".tar.bz2"):
with tarfile.open(dst, "w:bz2") as t:
for f in tmp.rglob("*"):
if f.is_file():
t.add(f, arcname=str(f.relative_to(tmp)))
elif dst_name.endswith(".7z"):
import py7zr
with py7zr.SevenZipFile(dst, "w") as z:
for f in tmp.rglob("*"):
if f.is_file():
z.write(f, str(f.relative_to(tmp)))
else:
return False, "неизвестный целевой формат"
return True, ""
except Exception as e:
return False, str(e)
finally:
shutil.rmtree(tmp, ignore_errors=True)
def convert(src: Path, dst: Path, target: str, ffmpeg_bin: str = "ffmpeg") -> tuple[bool, str]:
file_type = detect_type(src)
if file_type == "image":
return _convert_image(src, dst)
if file_type in ("video", "audio"):
return _convert_ffmpeg(src, dst, ffmpeg_bin)
if file_type == "archive":
return _convert_archive(src, dst)
# хуй знает что за файл — пусть ffmpeg попробует
return _convert_ffmpeg(src, dst, ffmpeg_bin)