«Качалка видео для Windows и Mac: почему TikTok не пускал собранную программу, а QuickTime принимал видео за музыку»

Вставляешь ссылку на видео с YouTube, TikTok, VK или Instagram, выбираешь 1080 p, 720 p или 480 p и жмёшь «Скачать». Через несколько секунд на Рабочем столе лежит mp4, который открывается в QuickTime, на айфоне и в любом плеере Windows. Пока идёт загрузка, по пиксельному офису бегает человечек и складывает в папку листки.
Это Качалка — бесплатная программа с открытым кодом. Никаких аккаунтов, рекламы и водяных знаков. Ниже — из чего она собрана и два бага, на которые я потратил больше всего времени: оба проявлялись только в собранной программе и ни разу при запуске из кода.
Давно хотел делать бесплатно для людей, время пришло.
Из чего собрано
yt‑dlp делает всю тяжёлую работу: достаёт видео с сотен сайтов.
FFmpeg склеивает видео со звуком. Он едет внутри программы через пакет
imageio-ffmpeg, так что ставить ничего не нужно.CustomTkinter рисует окно: тёмная тема, фиолетовые кнопки.
PyInstaller собирает
Kachalka.exeдля Windows иKachalka.appдля Mac.GitHub Actions собирает обе версии по тегу. У меня нет компьютера с Windows, так что exe я ни разу не собирал сам.
«Код писал вместе с Claude Code; идея, решения и проверка — мои».
Почему H.264, а не «лучшее качество»
Первое желание — качать максимум. Но YouTube отдаёт высокие разрешения в VP9 и AV1, а QuickTime и айфон такие файлы часто не открывают. Пользователь скачал видео, кликнул — а оно не играет. Поэтому Качалка всегда просит H.264 и звук AAC:
"format": "bv*+ba/b",
"format_sort": ["vcodec:h264", f"res:{height}", "acodec:m4a"],
"merge_output_format": "mp4",
Тонкость с res: yt‑dlp считает разрешение по меньшей стороне. Поэтому вертикальный TikTok 1080×1920 честно считается 1080 p и не отсекается фильтром «не больше 1080». Если бы я фильтровал по height, вертикальные видео скачивались бы в плохом качестве.
Цена решения — нет 4K: в H.264 его практически никто не отдаёт. Перекодировать 4K на компьютере пользователя — минуты ожидания, для простой качалки это лишнее.
Баг 1: TikTok пускает Python, но не пускает собранную программу
Из кода всё качалось. Собранная программа на той же ссылке падала:
ERROR: [TikTok] 7698495498537143604: Unexpected response from webpage request
Я последовательно исключал причины:
Сеть и VPN. VPN работает на весь компьютер, системного прокси нет. И скрипт, и программа ходят в сеть одинаково.
Окружение. Программа, запущенная из терминала, а не из Finder, падала так же.
Версия yt‑dlp. В логе сборки GitHub та же версия, что у меня, 2026.08.19.
Состав библиотек. Почти совпадал.
Остался Python. Локально у меня одна сборка Python, на GitHub — другая, с другими настройками TLS. По всей видимости, TikTok смотрит на «почерк» TLS‑соединения и одной сборке отдаёт нормальную страницу, а другой — заглушку. yt‑dlp, кстати, сам об этом предупреждал, а я не обратил внимания:
WARNING: [TikTok] The extractor is attempting impersonation,
but no impersonate target is available.
Решение — пакет curl_cffi: он умеет подключаться с отпечатком настоящего Chrome или Safari. Достаточно поставить yt‑dlp с нужным дополнением и не забыть положить пакет в сборку:
# requirements.txt
yt-dlp[default,curl-cffi]
# build.py
"--collect-all=curl_cffi",
Главный урок: проверять надо собранную программу, а не код. Локально ошибка не воспроизводилась ни разу. Я собрал пробную версию в отдельной ветке, скачал её из GitHub Actions и только после этого выпустил исправление.
Баг 2: видео из TikTok открывается как музыка
TikTok начал качаться, но в Finder вместо превью у файла была нота, а QuickTime видел в нём только звук. При этом ffprobe показывал нормальный H.264 1080×1080, а ffmpeg декодировал кадры без единой ошибки.
Странным было одно поле — масштаб времени видеодорожки:
stream|codec_type=video|time_base=1/5995319
Почти шесть миллионов делений в секунду. Я спросил у видеодвижка macOS напрямую, что он видит в файле:
asset = AVFoundation.AVURLAsset.URLAssetWithURL_options_(url, None)
len(asset.tracksWithMediaType_(AVFoundation.AVMediaTypeVideo))
# исходный файл — 0 видеодорожек
# после переупаковки — 1
Ноль. Для macOS видео в этом файле просто не существовало. Лечится переупаковкой без перекодирования: качество то же, работы на пару секунд.
ffmpeg -i in.mp4 -map 0 -c copy -video_track_timescale 90000 -movflags +faststart out.mp4
Теперь Качалка делает это после каждой загрузки. На Windows ffmpeg запускается с флагом CREATE_NO_WINDOW, иначе на секунду мелькает чёрное окно консоли.
Плавный прогресс, когда файлов два
YouTube отдаёт видео и звук отдельно, и прогресс yt‑dlp идёт дважды от 0 до 100%. Пользователь видит, как полоска доходит до конца и прыгает в ноль. Поэтому перед загрузкой Качалка смотрит размеры обеих частей и считает общий прогресс с весами. Полоска не прыгает к цели, а плавно догоняет её — каждые 16 мс проходит 12% оставшегося пути. Последние 3% оставлены на склейку.
Человечек в офисе
Гифка нарисована кодом на Pillow, без готовых ассетов. Холст 160×80 «пикселей» увеличивается в три раза без сглаживания. Офис — это кусок шириной 96 пикселей, который едет на 2 пикселя за кадр: за 48 кадров он проезжает ровно свою ширину, поэтому петля бесшовная. 48 кадров по 40 мс дают 25 кадров в секунду.
Ещё одна мелочь для Retina: CustomTkinter сам масштабирует картинки и размывает пиксель‑арт. Поэтому кадры заранее увеличиваются в два раза «по пикселям», и масштабировать окну уже нечего.
Сборка для двух систем без Windows
Одна матрица GitHub Actions: windows-latest собирает однофайловый exe, macos-latest — .app в zip. По тегу v* оба файла уходят в релиз, а к релизу прикладывается подпись происхождения. Любой может проверить, что файл собран из этого репозитория:
gh attestation verify Kachalka-Windows.exe --owner kozliq
Две вещи, которые пришлось объяснять в README:
Программа без платной подписи. Windows показывает «Система Windows защитила ваш компьютер», macOS — «не удаётся проверить разработчика». Это обходится за один шаг, но предупредить нужно.
Блокировки. Многие обходят их VPN‑расширением или DNS в настройках браузера. Программа настроек браузера не видит, и YouTube для неё недоступен. Поэтому сырые ошибки yt‑dlp переводятся на человеческий язык: вместо
getaddrinfo failedКачалка пишет «Нет связи с сайтом — похоже, он заблокирован у провайдера» и советует включить VPN для всего компьютера.
Что дальше
В планах значок в строке меню Mac: нажал, вставил ссылку, скачал — без большого окна.
Код и готовые файлы для Windows и Mac: github.com/kozliq/kachalka
Если что‑то не качается — заведите issue со ссылкой, разберусь.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.