VK
Control Center
Дашборд
Файлы
Массовые операции
Добавить сервер
Группы
Поиск
Telegram
Редактор файлов
VK
Rutube
Выбор файла
skip.txt
config.py
urls.txt
secrets.env
whitelist.txt
replace.txt
description_template.txt
vk_downloader.py
vk_uploader_films.py
Загрузить с сервера
Выберите сервер
vfilmecom (109.172.101.63)
Бывает и Так (149.154.70.133)
Общий (82.146.40.75)
Серверы для push
✓
✗
Имя
IP
Тематика
Группировать по владельцу
config.py
загружено с Бывает и Так
""" config.py — конфигурация TURBO VK 2in1 (режим: NO_SPLIT + native thumb). ЧТО МЕНЯТЬ ЗДЕСЬ: • Интервалы, лимиты, поведение batch controller • Параметры обложек (шрифт, цвет) • Список групп VK (GROUP_IDS) • Поведение uploader (задержка, лимиты) ЧТО МЕНЯТЬ В .env / secrets.env: • VK_ACCESS_TOKEN — токен для загрузки в VK • FIREFOX_PROFILE_DIR — путь к Firefox профилю с cookies YouTube • TG_BOT_TOKEN, TG_CHAT_ID — Telegram-уведомления • INSTANCE_NAME — имя этого сервера в уведомлениях """ import os from pathlib import Path # ── загрузка .env ───────────────────────────────────────────────────────────── try: from dotenv import load_dotenv as _load_dotenv _load_dotenv(Path(__file__).resolve().parent / ".env", override=False) _load_dotenv(Path(__file__).resolve().parent / "secrets.env", override=False) _load_dotenv(Path(__file__).resolve().parent / "user_settings.env", override=False) except ImportError: if (Path(__file__).resolve().parent / ".env").exists(): print( "[CONFIG] Hint: файл .env найден, но python-dotenv не установлен.\n" " Установи: pip install python-dotenv" ) def _env_float(name: str, default: float) -> float: raw = os.environ.get(name) if raw is None or raw.strip() == "": return default try: return float(raw.strip()) except ValueError: print(f"[CONFIG] WARN: {name} не число: {raw!r}, использую {default}") return default def _env_int(name: str, default: int) -> int: raw = os.environ.get(name) if raw is None or raw.strip() == "": return default try: return int(raw.strip()) except ValueError: print(f"[CONFIG] WARN: {name} не целое: {raw!r}, использую {default}") return default # ══════════════════════════════════════════════════════════════════════════════ # 1. ПУТИ # ══════════════════════════════════════════════════════════════════════════════ BASE_DIR = Path(__file__).resolve().parent VIDEOS_DIR = BASE_DIR / "Videos" WORK_DIR = BASE_DIR / "work" REST_DIR = VIDEOS_DIR / "_rest" STUCK_DIR = VIDEOS_DIR / "_stuck" # ══════════════════════════════════════════════════════════════════════════════ # 2. ИСПОЛНЯЕМЫЕ ФАЙЛЫ # ══════════════════════════════════════════════════════════════════════════════ PYTHON_EXE = "python3" YTDLP_EXE = "yt-dlp" FFMPEG_EXE = "ffmpeg" FFPROBE_EXE = "ffprobe" DOWNLOADER_SCRIPT = "vk_downloader.py" UPLOADER_SCRIPT = "vk_uploader_films.py" CONTROLLER_SCRIPT = "vk_batch_controller.py" # ══════════════════════════════════════════════════════════════════════════════ # 3. ФАЙЛЫ ДАННЫХ И ЛОГОВ # ══════════════════════════════════════════════════════════════════════════════ URLS_FILE = BASE_DIR / "urls.txt" CHUNK_FILE = BASE_DIR / "urls_chunk.txt" LOG_FILE = BASE_DIR / "log.txt" FAILED_FILE = BASE_DIR / "failed.txt" WHITELIST_FILE = BASE_DIR / "whitelist.txt" BLACKLIST_FILE = BASE_DIR / "blacklist.txt" SKIP_FILE = BASE_DIR / "skip.txt" SKIPPED_LOG = BASE_DIR / "skipped.txt" REPLACE_FILE = BASE_DIR / "replace.txt" PROXY_FILE = BASE_DIR / "proxy.txt" POST_LOG = BASE_DIR / "postprocess_log.txt" # ══════════════════════════════════════════════════════════════════════════════ # 4. BATCH CONTROLLER # ══════════════════════════════════════════════════════════════════════════════ BATCH_SIZE = 1 DOWNLOAD_CHUNK_SIZE = 1 SLEEP_HOURS = _env_int("SLEEP_HOURS", 0) SLEEP_MINUTES = _env_int("SLEEP_MINUTES", 0) VK_ACTIVE_HOURS_START = os.environ.get("VK_ACTIVE_HOURS_START", "00:00") VK_ACTIVE_HOURS_END = os.environ.get("VK_ACTIVE_HOURS_END", "23:59") VK_UPLOAD_MODE = os.environ.get("VK_UPLOAD_MODE", "randomized").strip().lower() if VK_UPLOAD_MODE not in ("randomized", "immediate"): VK_UPLOAD_MODE = "randomized" VK_RANDOMIZE_QUEUE = os.environ.get("VK_RANDOMIZE_QUEUE", "true").lower() == "true" VK_LONG_PAUSE_EVERY_N = _env_int("VK_LONG_PAUSE_EVERY_N", 0) # 0 = выключено VK_LONG_PAUSE_MIN_MINUTES = _env_int("VK_LONG_PAUSE_MIN_MINUTES", 60) VK_LONG_PAUSE_MAX_MINUTES = _env_int("VK_LONG_PAUSE_MAX_MINUTES", 120) VIDEO_EXTS = {".mp4", ".avi", ".mkv", ".mov", ".wmv", ".webm"} TEMP_SUFFIXES = (".part", ".tmp", ".ytdl", ".m4a.part", ".webm.part", ".mp4.part") TEMP_CONTAINS = (".fdash", ".f", ".temp", ".download", ".partial") STALE_TEMP_MINUTES = 1 UPLOADER_RETRIES = 2 # ══════════════════════════════════════════════════════════════════════════════ # 5. TELEGRAM-УВЕДОМЛЕНИЯ # ══════════════════════════════════════════════════════════════════════════════ INSTANCE_NAME = os.environ.get("INSTANCE_NAME", "Ubuntu | Ютуб") TG_ENABLED = os.environ.get("TG_ENABLED", "false").lower() == "true" TG_BOT_TOKEN = os.environ.get("TG_BOT_TOKEN", "") TG_CHAT_ID = os.environ.get("TG_CHAT_ID", "") TG_MODE = os.environ.get("TG_MODE", "full").strip().lower() or "full" if TG_MODE not in ("full", "send_only"): print(f"[CONFIG] WARN: TG_MODE={TG_MODE!r} неизвестен, использую 'full'") TG_MODE = "full" TG_SEND_ONLY_VERBOSE = os.environ.get("TG_SEND_ONLY_VERBOSE", "false").lower() == "true" TG_PROXY = os.environ.get("TG_PROXY", "").strip() TG_HEARTBEAT_HOURS = _env_float("TG_HEARTBEAT_HOURS", 1.0) TG_DISK_THRESHOLD_GB = _env_float("TG_DISK_THRESHOLD_GB", 10.0) TG_STALL_MINUTES = _env_int("TG_STALL_MINUTES", 30) TG_PERMERR_THRESHOLD = _env_int("TG_PERMERR_THRESHOLD", 5) TG_STATE_FILE = BASE_DIR / "tg_state.json" # ══════════════════════════════════════════════════════════════════════════════ # 6. DOWNLOADER # ══════════════════════════════════════════════════════════════════════════════ MAX_WORKERS_DOWNLOAD = 1 ABORT_AFTER_CONSECUTIVE_UNKNOWN = 30 VPN_AUTO_ROTATE = True VPN_LOCATIONS = ["PL", "NL", "DE", "FI", "LT", "EE"] MAX_RETRIES = 5 MIN_DURATION_MINUTES = 14 MAX_DURATION_MINUTES = 140 def _autodetect_firefox_profile_dir() -> str: """Поиск активного Firefox-профиля.""" candidates_roots: list[Path] = [] home = Path.home() appdata = os.environ.get("APPDATA") if appdata: candidates_roots.append(Path(appdata) / "Mozilla" / "Firefox") candidates_roots.append(home / "AppData" / "Roaming" / "Mozilla" / "Firefox") candidates_roots.append(home / ".mozilla" / "firefox") candidates_roots.append(home / "snap" / "firefox" / "common" / ".mozilla" / "firefox") candidates_roots.append(home / "Library" / "Application Support" / "Firefox") for root in candidates_roots: if not root.exists(): continue ini = root / "profiles.ini" if ini.exists(): try: import configparser cp = configparser.ConfigParser() cp.read(ini, encoding="utf-8") for section in cp.sections(): if section.lower().startswith("install") and cp.has_option(section, "Default"): rel = cp.get(section, "Default").strip() candidate = root / rel if candidate.exists() and (candidate / "cookies.sqlite").exists(): return str(candidate) for section in cp.sections(): if section.lower().startswith("profile") and cp.get(section, "Default", fallback="0") == "1": path = cp.get(section, "Path", fallback="").strip() if not path: continue is_relative = cp.get(section, "IsRelative", fallback="1") == "1" candidate = (root / path) if is_relative else Path(path) if candidate.exists() and (candidate / "cookies.sqlite").exists(): return str(candidate) except Exception: pass profiles = root / "Profiles" if profiles.exists(): for pattern in ("*.default-release", "*.default*", "*"): for p in sorted(profiles.glob(pattern)): if p.is_dir() and (p / "cookies.sqlite").exists(): return str(p) return "" _env_firefox = os.environ.get("FIREFOX_PROFILE_DIR", "").strip() if _env_firefox: FIREFOX_PROFILE_DIR = _env_firefox else: FIREFOX_PROFILE_DIR = _autodetect_firefox_profile_dir() if FIREFOX_PROFILE_DIR: pass # [CONFIG] Firefox profile auto-detected — куки не используются для YouTube TRANSLATE_TITLES = True TRANSLATE_TARGET_LANG = "ru" DOWNLOAD_THUMBNAILS = True THUMB_EXT = ".jpg" THUMB_MIN_BYTES = 10_000 THUMB_CANDIDATES = [ ("maxresdefault", 50_000), ("sddefault", 20_000), ("hqdefault", 15_000), ("mqdefault", 8_000), ("default", 3_000), ] # ══════════════════════════════════════════════════════════════════════════════ # 7. VK API # ══════════════════════════════════════════════════════════════════════════════ VK_ACCESS_TOKEN = os.environ.get("VK_ACCESS_TOKEN", "") VK_API_VERSION = "5.199" def _normalize_vk_proxy(raw: str) -> str: s = (raw or "").strip() if not s: return "" if "://" in s: return s parts = s.split(":") if len(parts) == 4: host, port, user, pwd = parts return f"http://{user}:{pwd}@{host}:{port}" if len(parts) == 2: host, port = parts return f"http://{host}:{port}" return s VK_UPLOAD_PROXY = _normalize_vk_proxy(os.environ.get("VK_UPLOAD_PROXY", "")) # ══════════════════════════════════════════════════════════════════════════════ # 8. ГРУППЫ VK # ══════════════════════════════════════════════════════════════════════════════ def _parse_group_ids(raw: str) -> list[int]: if not raw: return [] out: list[int] = [] for piece in raw.replace(";", ",").split(","): piece = piece.strip() if not piece: continue try: out.append(int(piece)) except ValueError: print(f"[CONFIG] WARN: VK_GROUP_IDS содержит нечисловое значение: {piece!r}") return out GROUP_IDS: list[int] = _parse_group_ids( os.environ.get("VK_GROUP_IDS", "235543773") ) if not GROUP_IDS: print( "[CONFIG] WARN: VK_GROUP_IDS пуст — uploader не сможет загрузить видео.\n" " Укажи ID группы в .env, например:\n" " VK_GROUP_IDS=235543773" ) # ══════════════════════════════════════════════════════════════════════════════ # 9. UPLOADER # ══════════════════════════════════════════════════════════════════════════════ UPLOAD_DELAY_MIN = _env_int("VK_UPLOAD_DELAY_MIN", 10) UPLOAD_DELAY_MAX = _env_int("VK_UPLOAD_DELAY_MAX", 30) UPLOAD_DELAY = UPLOAD_DELAY_MIN VK_DAILY_UPLOAD_LIMIT = _env_int("VK_DAILY_UPLOAD_LIMIT", 0) VK_WEEKEND_ENABLED = os.environ.get("VK_WEEKEND_ENABLED", "false").lower() == "true" VK_WEEKEND_DAILY_LIMIT = _env_int("VK_WEEKEND_DAILY_LIMIT", 0) VK_DRY_RUN = os.environ.get("VK_DRY_RUN", "false").lower() == "true" MAX_WORKERS_UPLOAD = 1 AUTO_DELETE = os.environ.get("VK_AUTO_DELETE", "true").lower() == "true" PUBLISH_ON_WALL = False SET_VIDEO_THUMBNAIL = True THUMB_SET_RETRIES = 5 THUMB_SET_RETRY_DELAY_SEC = 60 VIDEO_UPLOAD_RETRIES = 3 VIDEO_UPLOAD_RETRY_DELAY_SEC = 3 THUMB_EXTS = (".jpg", ".jpeg", ".png", ".webp") # ══════════════════════════════════════════════════════════════════════════════ # 10. POSTPROCESSOR (NO_SPLIT режим) # ══════════════════════════════════════════════════════════════════════════════ NO_SPLIT_MODE = True # всегда включён — серии не нарезаются THUMBNAIL_ENABLED = True THUMBNAIL_SAMPLE_COUNT = 5 THUMBNAIL_FONT_SIZE = 72 THUMBNAIL_TEXT_COLOR = "white" THUMBNAIL_STROKE_COLOR = "black" THUMBNAIL_TITLE_ENABLED = True THUMBNAIL_TITLE_FONT_SCALE = 1.2 THUMBNAIL_TITLE_MAX_LINES = 4 THUMBNAIL_TITLE_SAFE_WIDTH_RATIO = 0.85 THUMBNAIL_TITLE_MIN_FONT_SIZE = 34 THUMBNAIL_TITLE_YELLOW_WORDS = 2 THUMBNAIL_DARKEN_PERCENT = 10 THUMBNAIL_BLUR_PERCENT = 0 THUMBNAIL_SATURATION_PERCENT = 75 # ══════════════════════════════════════════════════════════════════════════════ # 11. НОРМАЛИЗАЦИЯ НАЗВАНИЙ # ══════════════════════════════════════════════════════════════════════════════ AUTO_NORMALIZE_MOVIE_TITLE = True REMOVE_RELEASE_TAGS = True REMOVE_MARKETING_PHRASES = True AUTO_TITLECASE = True THUMBNAIL_SHORT_TITLE = True MOVIE_TITLE_MAX_LEN = 80 THUMBNAIL_TITLE_MAX_LEN = 40 TITLE_MIN_MEANINGFUL_LEN = 3 TITLE_TRUNCATE_ON_COLON = True TITLE_TRUNCATE_ON_DASH = False # ══════════════════════════════════════════════════════════════════════════════ # 12. VK SOURCE # ══════════════════════════════════════════════════════════════════════════════ VK_ENABLED = True VK_USE_COOKIES = False
Перезапустить batch_controller после push
Push на выбранные серверы