Сделал программу для конвертации пиксель арта в идеальный свг
По работе нужно было много конвертировать пиксель арта в векторный формат и не найдя адекватных решений решил сам написать мини прожку, ну и от сырого прототипа в консоли за пару недель дошел до юзабельной версии с приятным интерфейсом.
Основным запросом было, что бы финальный SVG не лагал в той же самой фигме, так как нашему дизайнеру нужно было размещать логотипы в брендбуке лендинге и прочее. По этому я в первую очередь бросил все силы на алгоритм, помог мне довести его до идеала некий Гарри Тсанг, (его линкед ин можете найти в репозитории кому надо) который довел мой имеющийся алгоритм до идеала.
Принцип его работы довольно прост - сначала алгоритм смотрит на всю картинку и группирует пиксели по цветам. Далее мы ищем контур что бы четко определить границы каждого такого кластера одинаковых цветов. За счет эффективного математического расчета поиск этих границ занимает буквально секунды. Далее алгоритм ищет все прямые участки и например если граница идет прямо на 100 пикселей, он не будет ставить 100 точек а просто проведет линию от одного угла до другого. И по сути алгоритм не просто копирует пиксели, а скорее интерпретирует их как набор логических форм, по этому на выходе получается чистый и оптимизированный SVG за минимально затраченное время.
В общем на данный момент десктопная версия пополнилась еще парой фичей вроде импорта целых папок и конвертирования в формат WEBP, с помощью этих фич я конвертировал весь набор арт ассетов сайта, это где то 670 картинок за минут 5-7, в вебп формат и сохранил их в то же место где находились исходники.
Так же запустил по советам многих сайт, все вычисления проходят прямо в вашем браузере а не на сервере что позволяет проге быть почти полностью бесплатной.
Веб-сайт проги - https://glorp.art
На этом же сайте вы можете найти ссылки на репозиторий и страницу на итаче с готовым билдом
Программа полностью Open-Source и free to use
Ответ VoiceSpellCaster в «Почему я не использую папку "Мои документы"»2
Можно проще. Небольшая портативная программа FreeMove переносит файлы и папки в указанное место, а на старом месте оставляет символьные ссылки, которые может скрыть.
Очистка временных папок: cleanmgr /LOWDISK %systemroot%\system32\cmd.exe /c cleanmgr /sageset:65535 & cleanmgr /sagerun:65535
Если уж чистить, то вот ещё: Подчистить папку WinSxS
Для этого надо открыть командную строку от имени Администратора
(Win+X или клик правой кнопкой по меню пуск)
Ввести или скопировать и вставить команду:
Dism.exe /online /cleanup-image /StartComponentCleanup
Нажать на клавишу «Enter» и дождаться окончания процесса.
Dism.exe /online /Cleanup-Image /StartComponentCleanup /ResetBase
удаляет все замененные версии каждого компонента в хранилище компонентов.
Прога для скачки видео с вк по ссылке, забирайте ;-) Если надо могу доработать
Решил поделиться с вами простым кодом для скачки видео с вк без авторизации на сайте по ссылке (только открытое видео).
Могу по-быстрому накидать также для скачки музыки, видосов с ок.ру, или любого другого сайта)) Можно даже комбайн сделать - все в одном)
#!/usr/bin/env python3
"""
GUI для скачивания видео из ВКонтакте
Использует yt-dlp для загрузки видео
"""
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import threading
import os
import sys
from pathlib import Path
class VKVideoDownloader:
"""GUI приложение для скачивания видео из ВКонтакте"""
def __init__(self, root):
self.root = root
self.root.title("VK Video Downloader")
self.root.geometry("650x550")
self.root.resizable(True, True)
# Переменные
self.video_url = tk.StringVar()
self.download_path = tk.StringVar(value=os.path.expanduser("~/Downloads"))
# Флаг загрузки
self.is_downloading = False
self.setup_gui()
def setup_gui(self):
"""Настройка графического интерфейса"""
# Основной контейнер
main_frame = ttk.Frame(self.root, padding="15")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Настройка растягивания
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(5, weight=1)
# Заголовок
title_label = ttk.Label(
main_frame,
text="🎬 VK Video Downloader",
font=("Segoe UI", 18, "bold")
)
title_label.grid(row=0, column=0, pady=(0, 20))
# Поле ввода ссылки
url_label = ttk.Label(main_frame, text="Ссылка на видео:", font=("Segoe UI", 10))
url_label.grid(row=1, column=0, sticky=tk.W, pady=(0, 5))
url_frame = ttk.Frame(main_frame)
url_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=(0, 15))
url_frame.columnconfigure(0, weight=1)
self.url_entry = ttk.Entry(url_frame, textvariable=self.video_url, font=("Segoe UI", 10))
self.url_entry.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 10))
self.url_entry.bind("<Control-v>", self._on_paste)
self.url_entry.bind("<Control-V>", self._on_paste)
paste_btn = ttk.Button(url_frame, text="Вставить", command=self._paste_from_clipboard, width=8)
paste_btn.grid(row=0, column=1)
# Выбор папки для сохранения
folder_label = ttk.Label(main_frame, text="Папка сохранения:", font=("Segoe UI", 10))
folder_label.grid(row=3, column=0, sticky=tk.W, pady=(10, 5))
folder_frame = ttk.Frame(main_frame)
folder_frame.grid(row=4, column=0, sticky=(tk.W, tk.E), pady=(0, 15))
folder_frame.columnconfigure(0, weight=1)
self.folder_entry = ttk.Entry(folder_frame, textvariable=self.download_path, font=("Segoe UI", 9))
self.folder_entry.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 10))
browse_btn = ttk.Button(folder_frame, text="📁 Обзор...", command=self.browse_folder)
browse_btn.grid(row=0, column=1)
# Кнопка скачивания
self.download_btn = ttk.Button(
main_frame,
text="⬇️ Скачать видео (макс. качество)",
command=self.start_download,
style="Accent.TButton"
)
self.download_btn.grid(row=5, column=0, pady=(10, 15), sticky=(tk.W, tk.E))
# Прогресс бар
self.progress = ttk.Progressbar(main_frame, orient="horizontal", mode="determinate")
self.progress.grid(row=6, column=0, sticky=(tk.W, tk.E), pady=(10, 5))
# Статус
self.status_label = ttk.Label(main_frame, text="", font=("Segoe UI", 9))
self.status_label.grid(row=7, column=0, sticky=tk.W)
# Текстовое поле для логов
log_label = ttk.Label(main_frame, text="Лог:", font=("Segoe UI", 10))
log_label.grid(row=8, column=0, sticky=tk.W, pady=(15, 5))
self.log_text = tk.Text(main_frame, height=8, width=70, font=("Consolas", 9))
self.log_text.grid(row=9, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
# Скроллбар для логов
scrollbar = ttk.Scrollbar(main_frame, orient=tk.VERTICAL, command=self.log_text.yview)
scrollbar.grid(row=9, column=1, sticky=(tk.N, tk.S))
self.log_text.configure(yscrollcommand=scrollbar.set)
# Кнопка очистки логов
clear_btn = ttk.Button(main_frame, text="Очистить лог", command=self.clear_log)
clear_btn.grid(row=10, column=0, sticky=tk.E)
def _paste_from_clipboard(self):
"""Вставить из буфера обмена"""
try:
self.video_url.set(self.root.clipboard_get())
except tk.TclError:
pass
def _on_paste(self, event):
"""Обработка вставки"""
try:
self.url_entry.insert(tk.INSERT, self.url_entry.clipboard_get())
except tk.TclError:
pass
return "break"
def browse_folder(self):
"""Выбор папки для сохранения"""
folder_selected = filedialog.askdirectory(initialdir=self.download_path.get())
if folder_selected:
self.download_path.set(folder_selected)
def log(self, message: str):
"""Добавить сообщение в лог"""
self.log_text.insert(tk.END, message + "\n")
self.log_text.see(tk.END)
self.root.update_idletasks()
def clear_log(self):
"""Очистить лог"""
self.log_text.delete(1.0, tk.END)
def update_status(self, message: str):
"""Обновить статус"""
self.status_label.config(text=message)
def start_download(self):
"""Запустить загрузку"""
if self.is_downloading:
messagebox.showwarning("Предупреждение", "Загрузка уже выполняется!")
return
url = self.video_url.get().strip()
if not url:
messagebox.showerror("Ошибка", "Введите ссылку на видео!")
return
# Проверка URL
if "vk.com/video" not in url and "vk.com/clip" not in url:
messagebox.showwarning(
"Предупреждение",
"Ссылка может быть некорректной.\nОжидаемый формат: https://vk.com/video-XXXXXXX_XXXXXXXX"
)
self.is_downloading = True
self.download_btn.config(state="disabled", text="⏳ Загрузка...")
self.progress['value'] = 0
# Запуск в отдельном потоке
thread = threading.Thread(
target=self._download_worker,
args=(url, self.download_path.get()),
daemon=True
)
thread.start()
def _download_worker(self, url: str, output_path: str):
"""Рабочий поток для загрузки"""
try:
import yt_dlp
except ImportError:
self.root.after(0, lambda: messagebox.showerror(
"Ошибка",
"yt-dlp не установлен!\nУстановите: pip install yt-dlp"
))
self.root.after(0, self._reset_ui)
return
# Создаем папку если не существует
if output_path and not os.path.exists(output_path):
os.makedirs(output_path)
# Автовыбор лучшего качества с наибольшим разрешением
# VK использует раздельные потоки, поэтому выбираем лучший доступный формат
ydl_opts = {
'outtmpl': str(Path(output_path) / '%(title)s.%(ext)s'),
'format': 'bestvideo*+bestaudio/best',
'merge_output_format': 'mp4',
'progress_hooks': [self._progress_hook],
'quiet': True,
'no_warnings': True,
'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
}
self.root.after(0, lambda: self.log(f"🎬 URL: {url}"))
self.root.after(0, lambda: self.log(f"📁 Путь: {output_path}"))
self.root.after(0, lambda: self.log("📺 Качество: АВТО (максимальное)"))
self.root.after(0, lambda: self.log("-" * 50))
try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
# Получаем информацию о видео
self.root.after(0, lambda: self.log("⏳ Получение информации о видео..."))
info = ydl.extract_info(url, download=False)
self.root.after(0, lambda: self.log(f"📹 Название: {info.get('title', 'N/A')}"))
self.root.after(0, lambda: self.log(f"👤 Автор: {info.get('uploader', 'N/A')}"))
duration = info.get('duration')
if duration:
minutes = duration // 60
seconds = duration % 60
self.root.after(0, lambda: self.log(f"⏱️ Длительность: {minutes}:{seconds:02d}"))
self.root.after(0, lambda: self.log("-" * 50))
self.root.after(0, lambda: self.log("⬇️ Начало загрузки..."))
# Скачиваем
ydl.download([url])
self.root.after(0, lambda: self.log("=" * 50))
self.root.after(0, lambda: self.log("✅ Видео успешно скачано!"))
self.root.after(0, lambda: self.update_status("Готово!"))
self.root.after(0, lambda: messagebox.showinfo("Успех", "Видео успешно скачано!"))
except yt_dlp.utils.DownloadError as e:
error_msg = f"❌ Ошибка загрузки: {str(e)}"
self.root.after(0, lambda: self.log(error_msg))
self.root.after(0, lambda: self.log("\nВозможные причины:"))
self.root.after(0, lambda: self.log(" • Видео приватное (требуется авторизация)"))
self.root.after(0, lambda: self.log(" • Видео удалено или недоступно"))
self.root.after(0, lambda: self.log(" • Неверная ссылка"))
self.root.after(0, lambda: messagebox.showerror("Ошибка", f"Не удалось скачать видео:\n{str(e)}"))
self.root.after(0, lambda: self.update_status("Ошибка!"))
except Exception as e:
error_msg = f"❌ Произошла ошибка: {str(e)}"
self.root.after(0, lambda: self.log(error_msg))
self.root.after(0, lambda: messagebox.showerror("Ошибка", str(e)))
self.root.after(0, lambda: self.update_status("Ошибка!"))
finally:
self.root.after(0, self._reset_ui)
def _progress_hook(self, d: dict):
"""Хук для отображения прогресса"""
if d['status'] == 'downloading':
total = d.get('total_bytes') or d.get('total_bytes_estimate', 0)
downloaded = d.get('downloaded_bytes', 0)
if total > 0:
percent = (downloaded / total) * 100
speed = d.get('speed', 0)
self.root.after(0, lambda: self.progress.config(value=percent))
if speed:
speed_str = f"{speed / 1024 / 1024:.2f} MB/s"
else:
speed_str = "N/A"
self.root.after(0, lambda: self.update_status(f"⏳ Загрузка: {percent:.1f}% | {speed_str}"))
elif d['status'] == 'finished':
self.root.after(0, lambda: self.progress.config(value=100))
self.root.after(0, lambda: self.update_status("✅ Обработка..."))
self.root.after(0, lambda: self.log("✅ Загрузка завершена, обработка..."))
def _reset_ui(self):
"""Сброс UI после загрузки"""
self.is_downloading = False
self.download_btn.config(state="normal", text="⬇️ Скачать видео")
def main():
root = tk.Tk()
# Установка стиля
try:
import ttkbootstrap as ttkb
style = ttkb.Style(theme="cosmo")
except ImportError:
pass # Используем стандартный ttk
app = VKVideoDownloader(root)
root.mainloop()
if __name__ == "__main__":
main()
Написал приложение для трекинга игр, фильмов, аниме, сериалов
Всё началось с того, что мой бэклог игр жил в одном месте, список фильмов — в другом, аниме — в третьем. Надоело жонглировать, и я решил сделать одно приложение, где всё это живёт вместе. Так родился Tonkatsu Box.
Что умеет
Ищет по пяти базам данных: IGDB (250 000+ игр на 220 платформах — от Atari 2600 до PS5), TMDB (фильмы и сериалы), VNDB (визуальные новеллы), AniList (манга). Добавляешь в коллекцию, ставишь статус — играю, смотрю, прошёл, бросил, запланировал. Оценка от 1 до 10, личные заметки с мини-маркдауном (жирный, курсив, кликабельные ссылки).
Для сериалов и аниме есть трекер по эпизодам — отмечаешь серии, видишь прогресс по сезонам.
Импорт Steam
Подключаешь аккаунт — приложение подтягивает всю библиотеку, сопоставляет с IGDB, добавляет обложки и рейтинги. Наигранные часы сохраняются в заметках. DLC и саундтреки автоматически фильтруются. Повторный импорт не дублирует — обновляет данные.
Тир-листы
Моя любимая фича. Создаёшь тир-лист из коллекции, перетаскиваешь обложки в ряды S/A/B/C. Можно менять цвета, названия, добавлять свои ряды. Экспорт в PNG для шеринга. Работает для всего — игр, фильмов, аниме.
Почему сделал: люблю тир-листы, ненавижу их создавать. Обычно тратишь 20 минут на поиск обложек и загрузку — а тут всё уже есть из базы данных.
Визуальные доски
Свободный канвас — перетаскиваешь постеры, добавляешь заметки, рисуешь связи между элементами. Удобно для франшиз или планирования просмотров.
Готовые коллекции ретро-игр
Отдельно собрал полные библиотеки для 23 ретро-платформ — NES, SNES, Genesis, PS1, Game Boy и т.д. 25 000+ игр с обложками и рейтингами. Скачиваешь файл, импортируешь — видишь все игры, которые когда-либо выходили на платформе.
Главное
Все API ключи встроены — скачал, открыл, пользуйся. Без регистрации, без облака, без рекламы, без подписок. Все данные хранятся локально на устройстве, работает оффлайн. Open source.
Windows, Linux, Android.
Сейчас в работе интеграция с RetroAchievements и поддержка геймпада на Steam Deck.
Ссылки
Готовые коллекции: https://github.com/hacan359/tonkatsu-collections
Документация: https://github.com/hacan359/tonkatsu_box/wiki
Discord: https://discord.gg/JZVNPF7cS2
Буду рад фидбеку — что добавить, что неудобно, что сломалось.
Приложение использует IGDB (Twitch/Amazon), TMDB, VNDB, AniList. Из России некоторые API могут работать медленно.
Обзор ПК родителей на 775 сокете спустя 10 лет. Почему он до сих пор в строю? Жизнь после 4 ядра, 4 гига...
Привет пикабутяне и пикабутянки!
Начну с предыстории. Мы с братишкой приехали поступать на учебу из деревни в город, я после 11 класса, младший брат после 9 класса, родители для учебы купили нам системный блок на 775 сокете, это был 2009 год. В доме родителей, на тот момент, стоял системный блок на Celeron 2,0 Ггц + 512 ОЗУ - 2 плашками, видеокарта не помню модель на 256 Мб. Знаний на сборку ПК тогда не было, решили взять, увидев рекламу: 4 ядра, 4 гига. Скорее всего наши местные "торгаши" подсмотрели это у Эльдордо (если не ошибаюсь), поправьте меня, кто помнит, я из Казахстана, у нас местный канал гонял такую рекламу года 2-3.
Пошли мы покупать в этот салон, все красиво, конечно, выставка шикарная, мощь. Взяли монитор LG Full HD, системник: мать Foxconn, проц Q8200, ОЗУ 4 Гига + видеокарта 9800 GTX 512 Мб DDR3 память, HDD не помню уже объем. По началу все работало отлично, через месяц сгорела видеокарта Palit, они были горячие в тот период, может быть охлаждение было плохо организовано в корпусе. Нам ее поменяли, но подвох я понял поздно, поставили другого бренда на 1 Гиг, но с памятью DDR2. Такой себе был магазин, при нас туда приносили много чего по гарантии, в момент эпизода с видеокартой и когда мы пошли снова, колонка перестала работать от этого же компьютера.
Позже мы с братишкой, приблизительно заканчивая универ, собрали себе комп на i5-2400 и 560 GTX от Inno3D (она выдала отвал после запуска Ведьмака 3, года через 3-4).
Так вот, остался наш 775 ПК, который мы отдали родителям в деревню, на замену Celeron, который к тому моменту был уже достаточно древним. Позже решили его заменить 775, но уже за копейки из Китая - Q8400, примерно 2016 год, плату также поменяли, так как мне за недорого досталась DDR3 память по 4 Гб двумя плашками. Видеокарту по какой причине заменили, уже не помню, поставили 240 GT. Отец играет редко и во что-то старое, поэтому его устраивает. Так же был установлен китайский SSD (Kingmax или что-то такое), который отработал свое до конца 2025 года, затем выдал ошибку в Crystaldisk.
У меня и у родителей сейчас нет возможности купить другой SSD, нашел живой HDD на 1Tb и перенес туда систему методом клонирования, также установил игры, нашел диск, который когда-то нам покупали, любимая серия отца - Return to Castle Wolfenstein и моды (8 в одном). Что еще интересного в этом сборнике, Ghost Recon, я не играл эту серию игр, и Serious Sam, в том числе вторая часть, мне она очень нравилась раньше, сейчас устарела конечно же. Она с корявым русско-немецким переводом на пиратском диске, нам его покупали в период студенчества в подарок. Забавный артефакт той эпохи.
Как пользуются этим компьютером мои родители, мама использует для распечатки документов, поиска информации в интернете, просмотра фильмов, отец в основном слушает музыку и редко играет во что-то типа Medal of honor, старые части Call of Duty. Для Базовых задач им такого системника хватает, они другой не хотят. Ютуб работает хорошо, игры 00х тоже. Нареканий нет. В таком режиме использования: 775 сокет реально вечен? Что думаете Вы?
Забыл сказать, что плата покупалась новая ноунейм какая-то, в местном магазине была. Я все же соберу что-то по мощнее родителям, в будущем, думаю на AM4 сокете.
Также можно посмотреть разбор и игры на этом ПК в моем видео:
Всем спасибо за просмотр и прочтение!
Ответ на пост «Почему я не использую папку "Мои документы"»2
Делюсь своим хаком для переноса папок сохранений и прочего, которые захламили диск C.
Есть разные программы типа WinDirStat (просто один из примеров) для простого анализа самых жирных папок.
И когда оказывается, что тот же "The Riftbreaker" скушал целый гиг (для меня это чувствительно), то начинается операция "Перенос".
Я делаю для игры вид, что она все еще работает с сохранками на диске C. Но я на самом деле переношу на диск, например E.
Заранее я сделал папки по такому пути для подобных переносов:
E:\AppData\Local
Открываем командную строку.
И поочередно выполняем команды.
xcopy "C:\Users\ИМЯ ПОЛЬЗОВАТЕЛЯ\Documents\The Riftbreaker" "E:\AppData\Local\The Riftbreaker" /E /I /H /Y
"The Riftbreaker" - для примера. У вас это будет название папки с сохранениями игры или даже со всей программой: например, папка мессенджера, если тот не дает установить себя никуда кроме C, но нужен для общения с родственниками...
Т.е. в первых кавычках путь к папке с информацией для переноса указываете, а в другом путь к новому месту.
rmdir "C:\Users\ИМЯ ПОЛЬЗОВАТЕЛЯ\Documents\The Riftbreaker" /S /Q
Это опять путь к прошлому месту.
mklink /D "C:\Users\ИМЯ ПОЛЬЗОВАТЕЛЯ\Documents\The Riftbreaker" "E:\AppData\Local\The Riftbreaker"
Аналогичные пути как в первой команде.
Еще за лет 5 в папке Temp набралось на гигов 20. Советую глянуть. Там точно часть файлов хотя бы удалить можно. Если система не почистила.
Всем приятного очищения системного диска!







