Ресайз і відеообробка — це не “прикрутити бібліотеку”. Це важкі операції, які легко зламати, якщо робити їх прямо в контролері. Контролер — це HTTP-шар. Він має швидко прийняти запит, провалідувати, зберегти файл, запустити обробку і повернути відповідь. Якщо ви починаєте в контролері відкривати зображення, крутити розміри, запускати FFMpeg, генерувати прев’ю — ви отримаєте: повільні відповіді, таймаути, нестабільність на піках, хаос логіки, дублювання коду в різних місцях і, найгірше, неможливість нормально відлагоджувати та повторювати обробку.
Правильна модель мислення така: ресайз і FFMpeg — це інтеграція в pipeline, а не “частина контролера”. Тобто у вас є єдиний pipeline завантаження: validate → store original → create media record → dispatch processing → return. А сама обробка — або в сервісі, або (краще) в чергах через Job. Контролер не повинен знати, які саме розміри ви робите, які формати підтримуєте і як виглядають команди FFMpeg. Він повинен викликати один метод сервісу типу “прийняти медіа”, а решту робить інфраструктура.
Починаємо із зображень. У CMS вам майже завжди потрібні кілька “похідних” версій: thumbnail для списків, medium для карточок, large для hero. Якщо ви зберігаєте тільки оригінал, ви або будете роздавати гігантські картинки (повільно), або будете на льоту змінювати розміри при кожному запиті (ще гірше). Тому стандарт: зберігаємо оригінал, а деривативи генеруємо один раз при завантаженні або при першому використанні — і кешуємо як файли.
Ключове правило: оригінал ніколи не перезаписуйте як “єдину” версію. Оригінал — це джерело. Похідні — це артефакти. Чому це важливо: сьогодні вам потрібні одні розміри і формат, завтра ви зміните дизайн і захочете інші. Якщо у вас є оригінал, ви можете перегенерувати похідні. Якщо оригінал знищений або вже “пережатий” — ви втратили якість і можливість переробки.
Далі — де саме робити ресайз. Якщо файли маленькі і трафік незначний, можна ресайзити синхронно в сервісі під час upload, але це компроміс і він почне боліти при зростанні. Правильний шлях для майбутнього — відразу закладати асинхронну обробку через чергу: контролер зберіг оригінал, повернув відповідь, а job у фоні зробив thumbnail/medium/large. Це робить UI швидким, а обробку — керованою.
Щоб це працювало як система, вам потрібні стани медіа. Наприклад: uploaded (оригінал збережено), processing (генеруємо похідні), ready (все готово), failed (обробка впала). Це не “надлишок”, це контроль. Інакше ви не знатимете, чи готова картинка, чому не показується прев’ю, чи треба повторити job.
Тепер відео і FFMpeg. Відео завжди важче за картинки. Тут майже неминуче потрібні черги. Якщо ви запускаєте FFMpeg в HTTP-запиті, ви будете ловити таймаути і “випадкові” падіння на різних розмірах файлів. Стандартний pipeline для відео: зберегли оригінал → записали в БД метадані → диспатчили job на транскодинг/прев’ю → UI показує “обробляється” → по завершенню статус стає ready, і фронт починає використовувати готові файли.
Відеообробка зазвичай включає: перевірку тривалості/розміру, генерацію постера (thumbnail кадру), інколи — транскодинг у єдиний формат (наприклад mp4/h264 або webm), і, якщо потрібно для стрімінгу, HLS/DASH. Для навчання вам не треба заходити в HLS одразу, але важливо засвоїти принцип: FFMpeg — це окремий етап, який має бути відокремлений від контролера і мати повторюваність.
“Без магії” означає конкретну структуру. У вас є MediaService (або UploadService), який робить тільки керовані речі: прийняти файл, зберегти, створити запис у media, визначити тип (image/video), поставити в чергу відповідний job. Далі є ProcessImageJob і ProcessVideoJob. Вони отримують id медіа, читають його дані з БД, беруть файл зі Storage, запускають обробку, зберігають похідні файли, оновлюють метадані і статус. Контролер про ці деталі не знає.
Ще один важливий момент — повторюваність і дебаг. Обробка може впасти: не той codec, битий файл, не вистачило пам’яті, FFMpeg не встановлений, помилка прав доступу до storage. Якщо вся логіка в контролері, ви отримаєте “500” для користувача і невідомо що в логах. Якщо це job — у вас є failed jobs, retries, таймаути, логування, і ви можете перезапустити. Це вже продакшен-стандарт.
Таймаути й ліміти — окрема дисципліна. Для зображень ставте розумні обмеження по розміру і по максимальним dimensions, інакше можна завантажити “монстра”, який покладе процес обробки. Для відео обмежуйте розмір і тривалість (хоча б логічно), і налаштовуйте timeout job. Якщо у вас є можливість, транскодинг краще робити на окремих воркерах. Навіть на малому проєкті варто мислити так, бо потім це важко “відрізати” від веба.
Зберігання похідних. Для зображень нормальний стандарт: окремі файли в підпапках або з суфіксами, типу thumb_, md_, lg_. Для відео: окремо poster, окремо transcoded file. У БД бажано мати поля: original_path, thumb_path, medium_path, large_path, poster_path, duration, width, height, mime, size, status, error_message. Це дає вам контроль і не змушує кожного разу “вираховувати” шлях.
З точки зору UI, важливо показувати стан. Якщо медіа processing, ви показуєте заглушку і не намагаєтеся віддати файл, якого ще немає. Якщо failed — показуєте повідомлення і даєте кнопку “повторити” (для адміна). Це дрібниця, але вона прибирає хаос “у когось показало, у когось ні”.
Висновок: ресайз зображень і FFMpeg — це частина медіа-пайплайна, а не “шматок коду в контролері”. Контролер має залишатися тонким: прийняв → провалідував → зберіг → запустив обробку → повернув відповідь. Вся “важка” логіка — в сервісі й jobs, з чіткими статусами, логуванням і можливістю повтору. Якщо ви закладете це зараз, проєкт спокійно переживе зростання медіа, трафіку і вимог до форматів, без постійних таймаутів і пожеж.