wp-content/uploads، فرمت، ابعاد، مسیرهای فارسی و Unicode و امکان بازگشت به نسخه اصلی حفظ شود؛ در عین حال خروجی باید آنقدر کوچکتر باشد که جایگزینی و آپلود دوباره آن واقعاً ارزش عملی داشته باشد.فرمتهای رایج تصویر
قبل از ورود به خود پروژه، باید یک نکته پایه روشن باشد: فرمت تصویر فقط پسوند فایل نیست. JPEG، PNG، WebP و AVIF روشهای متفاوتی برای ذخیره داده تصویری دارند و به همین دلیل یک تنظیم یا Encoder واحد الزاماً روی همه آنها نتیجه مشابهی نمیدهد. همین تفاوت در این پروژه بهصورت عملی دیده شد؛ مسیری که برای JPG مفید بود، برای بیشتر PNGها تقریباً هیچ سودی ایجاد نکرد.
اگر میخواهید قبل از ادامه مقاله ساختار، کاربرد و تفاوت این فرمتها را عمیقتر بشناسید، راهنمای بررسی فرمتهای تصویری و بهترین انتخاب در DJH.ir مرجع کاملتری برای خود Formatهاست. در این مقاله فقط به اندازهای وارد جزئیات میشویم که منطق تصمیمهای پروژه روشن شود.
| فرمت | مزیت اصلی | محدودیت مهم |
|---|---|---|
| JPEG / JPG | حجم مناسب برای عکسهای واقعی و امکان فشردهسازی Lossy مؤثر | شفافیت ندارد و Re-encode مکرر میتواند افت کیفیت را بیشتر کند |
| PNG | Lossless، مناسب شفافیت، Screenshot، نمودار و گرافیک | برای عکسهای پیچیده معمولاً بزرگتر است و نیاز به Optimizer مناسب دارد |
| WebP | پشتیبانی از Lossy، Lossless و Transparency با حجم مناسب | تبدیل همه فایلهای قدیمی به WebP میتواند Workflow و URLهای موجود را تغییر دهد |
| AVIF | فشردهسازی بسیار قوی در بسیاری از تصاویر و پشتیبانی از قابلیتهای مدرن | Encode سنگینتر است و مهاجرت یک سایت قدیمی به آن باید جداگانه Validate شود |
JPEG برای عکسها
JPEG معمولاً برای عکسهای دوربین، تصاویر پرجزئیات و محتوایی که میلیونها رنگ دارد مناسب است. این فرمت غالباً با فشردهسازی Lossy استفاده میشود؛ یعنی برای کاهش حجم بخشی از اطلاعات تصویری حذف میشود. نکته مهم این است که «Lossy» لزوماً به معنی تصویر بد نیست؛ اگر Quality درست انتخاب شود، میتوان حجم را بهطور محسوسی کم کرد بدون اینکه افت کیفیت در استفاده واقعی آزاردهنده باشد.
PNG برای گرافیک و شفافیت
PNG ذاتاً انتخاب متفاوتی است. مسیر معمول آن Lossless است و برای لوگو، متن، Screenshot، نمودار، Illustration و تصاویر دارای Alpha کاربرد زیادی دارد. به همین دلیل استفاده از همان روشی که JPEG را خوب کوچک میکند، الزاماً برای PNG نتیجه نمیدهد. در پروژه حاضر همین تفاوت باعث شد Engine مخصوص PNG تغییر کند.
WebP و AVIF
WebP و AVIF فرمتهای مدرنتری هستند و در بسیاری از سناریوها میتوانند فایل کوچکتری تولید کنند. با این حال هدف این پروژه «تغییر فرمت» نبود؛ Requirement اصلی حفظ نام، Extension و ساختار فعلی بود. بنابراین WebPهای موجود Optimize شدند، اما JPG و PNG به WebP یا AVIF تبدیل نشدند. تبدیل فرمت باید بهعنوان یک پروژه Migration جدا، همراه با بررسی Browser، WordPress، Cache، URL و Thumbnailها انجام شود.
مسئله یک سایت قدیمی
در یک سایت تازه، معمولاً میتوان از همان ابتدا سیاست مشخصی برای ابعاد، فرمت و فشردهسازی تصاویر تعریف کرد. اما سایت قدیمی ممکن است سالها فایل با تنظیمات متفاوت جمع کرده باشد. بعضی تصاویر از قبل خوب فشرده شدهاند، بعضی PNG هستند، بعضی JPG، بخشی WebP شدهاند و نام فایلها یا مسیرها نیز ممکن است فارسی باشند. اگر قرار باشد این آرشیو دوباره روی همان WordPress قرار گیرد، تغییر بیبرنامه نام یا ساختار پوشه میتواند مشکل جدیدی ایجاد کند.
برای این پروژه چند محدودیت از ابتدا غیرقابل تغییر بود. هدف Resize یا مهاجرت همه تصاویر به WebP و AVIF نبود؛ هدف این بود که همان فایلها تا جای ممکن سبکتر شوند و قابلیت Rollback نیز از بین نرود.
| محدودیت | تصمیم پروژه | دلیل |
|---|---|---|
| ابعاد | بدون تغییر | جلوگیری از تغییر رفتار صفحات |
| فرمت و نام | حفظ شود | سازگاری با ساختار موجود |
| نسخه اصلی | .original.* | Rollback و Retest |
| جایگزینی | فقط Candidate معتبر و کوچکتر | جلوگیری از Regression |
| مسیر | حفظ ساختار uploads | آمادهسازی برای Re-upload |
چرا فقط افزونه کافی نبود؟
افزونههای WordPress برای بسیاری از سایتها انتخاب خوبی هستند، مخصوصاً وقتی هدف Optimize کردن Media Library فعال و تصاویر جدید باشد. اما مسئله این پروژه متفاوت بود: یک Corpus موجود باید خارج از WordPress، با کنترل دقیق روی فایلسیستم، Backup، Candidate، Validation، گزارشگیری و امکان اجرای مجدد پردازش میشد. بنابراین ابزار اصلی یک Workflow مستقل PowerShell بود، نه یک Plugin داخل پنل مدیریت.
این به معنی برتری مطلق روش اسکریپتی نیست. اگر سایت فعال است و میخواهید تصاویر جدید بهصورت خودکار Optimize شوند، Plugin یا Image CDN ممکن است عملیتر باشد. این پروژه برای یک عملیات Batch روی آرشیو موجود طراحی شد.
معماری امن پروژه
قانون مرکزی Pipeline ساده بود: هیچ Encoder حق نداشت مستقیم فایل اصلی را نابود کند. ابتدا یک Candidate جدا ساخته میشد، سپس اعتبار آن بررسی میشد و فقط اگر خروجی معتبر و کوچکتر از Source بود، نسخه اصلی با نام .original.ext نگهداری و Candidate با نام اصلی جایگزین میشد.
Source
↓
Create Candidate
↓
Validate Candidate
↓
Compare Size
↓
Smaller + Valid? ── No ──> Keep Source
│
Yes
↓
Source -> .original.ext
Candidate -> Original Filename
↓
Log ResultCandidate و Validation
ساخته شدن فایل خروجی بهتنهایی نشانه موفقیت نیست. Candidate باید واقعاً بهعنوان تصویر قابل خواندن باشد. در این پروژه FFprobe برای Validation فرمتهایی که با FFmpeg پردازش میشدند به کار رفت و برای مسیر PNG نیز خروجی ابزار تخصصی قبل از پذیرش نهایی بررسی میشد.
Backup و Rollback
Backup فقط برای روز مبادا نبود. Restore بخشی از روش Benchmark شد. وقتی Engine یا تنظیمات تغییر میکرد، باید دوباره از Source واقعی شروع میکردیم؛ مقایسه یک Encoder جدید با خروجی قبلاً Re-encodeشده نتیجه قابل اتکایی نمیدهد.
قانون کوچکتر بودن
اگر Candidate حتی یک تصویر کاملاً معتبر بود اما حجم بیشتری داشت، Source حفظ میشد. این وضعیت Error محسوب نمیشد؛ یک فایل ممکن است از قبل بهاندازه کافی فشرده باشد و Re-encode آن هیچ مزیتی ایجاد نکند.
جمعآوری تصاویر
مرحله اول پروژه ساخت Downloader بود. 861 URL مستقیماً داخل اسکریپت قرار گرفت تا اجرای نهایی به فایل Excel یا منبع جانبی وابسته نباشد. مسیر هر URL بعد از wp-content/uploads/ استخراج و همان ساختار روی دیسک ساخته شد.
wp-content\uploads\2025\05\3.webpاین تصمیم برای Re-upload مهم بود، چون خروجی صرفاً یک پوشه تخت از تصاویر نبود؛ ساختار واقعی WordPress حفظ میشد. Run Downloader با 861 دانلود موفق، صفر Existing و صفر Failure تمام شد.
URLs : 861
Downloaded : 861
Existing : 0
Failed : 0اختلاف 861 و 860
پس از انتقال فایلها به Root عملی پروژه، Optimizer تعداد 860 تصویر قابل پردازش پیدا کرد. اطلاعات موجود علت دقیق اختلاف یک فایل را ثبت نکرده است. بنابراین نسبت دادن آن به فایل خراب، فرمت خاص یا خطای دانلود درست نیست؛ تنها چیزی که با Evidence پروژه میتوان گفت این است که 861 URL دانلود شد و Corpus پردازششده Optimizer شامل 860 تصویر بود.
روشهای کاهش حجم
کاهش حجم تصویر یک تکنیک واحد نیست. در این پروژه چند روش متفاوت استفاده شد و هرکدام نقش مشخصی داشتند. بعضی روشها کاملاً Lossless بودند، بعضی عمداً از فشردهسازی Lossy استفاده میکردند و برای PNG نیز یک مسیر Palette با Quality Guard آزمایش شد. انتخاب روش بر اساس Format، نوع تصویر و نتیجه واقعی Candidate انجام میشد.
فشردهسازی Lossless
در فشردهسازی بدون اتلاف یا Lossless، داده تصویری قابل بازسازی باقی میماند و هدف این است که همان تصویر با ساختار فشردهتری ذخیره شود. OxiPNG در مسیر اصلی PNG پروژه دقیقاً همین نقش را داشت. مزیت Lossless این است که افت بصری ایجاد نمیکند؛ محدودیتش این است که اگر Source از قبل خوب Optimize شده باشد، فضای زیادی برای کاهش حجم باقی نمیماند.
فشردهسازی Lossy
JPEG و WebP در این پروژه با تنظیمات Lossy پردازش شدند. در این روش Encoder بخشی از اطلاعات کماهمیتتر را حذف میکند تا فایل کوچکتر شود. هرچه تنظیمات تهاجمیتر باشند، معمولاً حجم کمتر و ریسک Artifact بیشتر میشود. به همین دلیل مقدار qscale یا Quality یک «عدد جادویی» برای همه سایتها نیست و باید روی Sample واقعی همان Corpus آزمایش شود.
کاهش رنگ با Palette
برای تعدادی از PNGها، Candidate پالت 256 رنگ نیز ساخته شد. این روش میتواند برای Screenshot، نمودار، آیکن یا گرافیکهایی که تعداد رنگ واقعی محدودی دارند کاهش حجم خوبی ایجاد کند. اما چون تبدیل تصویر Full-color به Palette ممکن است اختلاف بصری ایجاد کند، در این پروژه Candidate پالت فقط در صورت عبور از RMSE Guard اجازه پذیرش داشت.
حذف دادههای غیرضروری
بخشی از کاهش حجم PNG میتواند از Optimization ساختار داخلی، فیلترها و حذف Metadata غیرضروری به دست بیاید. تنظیم safe برای Strip در مسیر OxiPNG انتخاب شد تا پروژه بهجای حذف تهاجمی اطلاعات، روی دادههایی تمرکز کند که برای نمایش تصویر لازم نیستند. این بخش نیز باید متناسب با نیاز سایت تنظیم شود؛ مثلاً اگر Metadata خاصی برای Workflow دیگری لازم است نباید کورکورانه حذف شود.
بدون Resize
یکی از رایجترین روشهای کاهش حجم، کم کردن Resolution است؛ اما در این پروژه عمداً Resize انجام نشد. دلیل آن Constraint پروژه بود: ابعاد فایل باید دقیقاً حفظ میشد. در سایت دیگر، اگر تصاویر بسیار بزرگتر از اندازه واقعی نمایش باشند، Resize صحیح میتواند حتی از Re-encoding مؤثرتر باشد.
بدون تغییر فرمت
روش دیگر، تبدیل JPEG یا PNG به WebP یا AVIF است. این کار در پروژه حاضر انجام نشد چون Extension نهایی باید همان Extension اولیه باقی میماند. بنابراین Saving گزارششده نتیجه Optimize کردن فایلها در همان Format است، نه Migration به یک Format جدید. برای شناخت تفاوت این دو رویکرد، مقاله راهنمای فرمتهای تصویری مکمل مناسبی است.
از نظر فلسفه پردازش Batch، این پروژه شباهت زیادی به پروژه عملی کاهش حجم آرشیو موسیقی با FFmpeg دارد: Source حفظ میشود، تنظیمات روی داده واقعی سنجیده میشوند و نتیجه بر اساس حجم کل Corpus ارزیابی میشود، نه یک نمونه انتخابی.
اولین Run
نسخه اولیه Optimizer تقریباً همه فرمتها را از مسیر FFmpeg عبور میداد. معماری Candidate و Backup از همان ابتدا ایمن بود، اما نتیجه حجمی نشان داد انتخاب یک Engine واحد برای همه فرمتها تصمیم مناسبی نیست.
Images found : 860
Optimized : 465
No size benefit : 395
Already processed : 0
Failed : 0
Before optimized : 48.67 MB
After optimized : 43.33 MB
Total saved : 5.33 MB (11.0%)در ظاهر 11 درصد Saving بد نبود، اما این Summary یک محدودیت مهم داشت: Before optimized فقط حجم فایلهایی را جمع میکرد که واقعاً Replace شده بودند، نه کل 860 تصویر. بنابراین برای تصمیمگیری درباره کل Corpus کافی نبود. در نسخههای بعدی دو شاخص مستقل برای «فایلهای Replaceشده» و «کل Corpus» اضافه شد.
مشکل PNG
برای پیدا کردن منشأ 395 مورد No size benefit، گزارش به تفکیک فرمت توسعه پیدا کرد. همین کار مسیر پروژه را عوض کرد. JPG و WebP تا حد قابل قبولی با FFmpeg کوچک میشدند، اما PNG تقریباً هیچ سودی نداشت.
| فرمت | Run اولیه | برداشت |
|---|---|---|
| JPG | 442 از 591 | FFmpeg مؤثر بود |
| PNG | 4 از 246 | Engine نامناسب برای این Corpus |
| WebP | 19 از 23 | نتیجه قابل قبول |
تحلیل بر اساس فرمت
از 246 PNG فقط چهار فایل Candidate کوچکتر داشتند و 242 مورد هیچ Benefit حجمی ایجاد نکردند. این داده مهمتر از درصد Saving همان چهار فایل موفق بود. اگر فقط میانگین Saving فایلهای Replaceشده دیده میشد، ممکن بود نتیجه اشتباه گرفته شود.
چرا درصد PNG گمراهکننده بود؟
Summary اولیه برای چهار PNG موفق Saving بالایی نشان میداد، اما سؤال درست این نبود که «فایلهای موفق چند درصد کوچک شدند؟». سؤال مهم این بود که «چه سهمی از Corpus با این روش واقعاً قابل بهینهسازی است؟». وقتی 242 فایل از 246 مورد هیچ Candidate بهتری ندارند، باید خود معماری بررسی شود.
Restore قبل از Retest
بعد از هر تغییر جدی در Engine، آزمایش باید روی Originalها تکرار میشد. برای همین اسکریپت Restore تمام فایلهای *.original.* را پیدا میکرد، نسخه Optimizeشده فعلی را حذف میکرد و Source اصلی را به نام اولیه برمیگرداند.
Backups found : 465
Restored : 465
Failed : 0این مرحله از نظر فنی ساده به نظر میرسد، اما برای Benchmark اهمیت زیادی دارد. Re-encode کردن خروجی قبلی میتواند هم کیفیت را تغییر دهد و هم مقایسه حجم را منحرف کند. اگر قرار است دو روش Compression مقایسه شوند، هر دو باید تا حد امکان از یک Source یکسان شروع کنند.
آزمون ImageMagick
بعد از مشخص شدن ضعف مسیر FFmpeg برای PNGهای این Corpus، ImageMagick بهعنوان گزینه بعدی آزمایش شد. ایده اولیه این بود که چند Candidate Lossless با Strategyهای مختلف ساخته شود و در کنار آن یک Candidate پالت 256 رنگ نیز تولید شود. Candidate پالت فقط در صورتی میتوانست برنده شود که اختلاف تصویری آن از Quality Guard مبتنی بر RMSE عبور کند.
مشکل PNG32
در یکی از نسخهها خروجی Lossless با PNG32: نوشته میشد. این اجبار میتوانست PNGهایی را که ذاتاً Palette یا Grayscale بودند به RGBA 32-bit تبدیل کند و بهجای کاهش حجم، فایل بزرگتری بسازد. بعد از مشاهده این رفتار، اجبار PNG32 حذف شد و Color Type اصلی تا حد امکان حفظ شد.
مشکل سرعت
اصلاح PNG32 مشکل اصلی Performance را حل نکرد. معماری brute-force برای هر PNG چند Process مستقل ImageMagick اجرا میکرد: Candidateهای مختلف، Compare و RMSE. روی Batch شامل 246 PNG، هزینه Process launch و تکرار Encode به گلوگاه تبدیل شد.
[22:37:28] [FILE ] 125/860 | ...\10-5.png
[22:37:32] [PNG ] Winner | mode=LOSSLESS_F0_S0 | candidate=109.7 KB | RMSE=0
[22:37:32] [KEEP ] No benefit | source=108.4 KB candidate=109.7 KB
[22:37:39] [FILE ] 128/860 | ...\12-3.png
[22:37:48] [PNG ] Winner | mode=LOSSLESS_F0_S0 | candidate=115.6 KB | RMSE=0چرا کنار گذاشته شد؟
ImageMagick از پروژه حذف نشد؛ نقش آن تغییر کرد. مسئله این نبود که ImageMagick نمیتواند PNG را پردازش کند، بلکه استفاده از آن بهعنوان موتور brute-force اصلی برای این Batch هزینه زمانی نامناسبی داشت. راهحل بهتر این بود که مسیر معمول PNG به یک Optimizer تخصصی واگذار شود و ImageMagick فقط در سناریویی که واقعاً مزیت اضافه دارد اجرا شود.
برای آشنایی عمیقتر با خود ابزار، مقاله آموزش ImageMagick صفر تا صد جزئیات بیشتری درباره پردازش تصویر و فرمانهای آن دارد.
مهاجرت به OxiPNG
موتور اصلی PNG در معماری نهایی به OxiPNG 10.1.1 تغییر کرد. OxiPNG در مسیر معمول یک Candidate Lossless میساخت و ImageMagick فقط برای Palette fallback وارد میشد. به این ترتیب هزینه چندین Encode عمومی برای هر فایل حذف شد، ولی امکان پیدا کردن Candidate پالت کمحجمتر همچنان باقی ماند.
مسیر Lossless
OxiPNG با Optimization Level مشخص روی نسخه موقت کار میکرد. اگر Candidate معتبر و کوچکتر از Source بود، میتوانست بهعنوان Winner پذیرفته شود. برای این مسیر، تغییر بصری وجود نداشت و RMSE معادل صفر بود.
Palette fallback
در کنار Candidate Lossless، امکان تبدیل کنترلشده به Palette 256 رنگ نیز وجود داشت. این مسیر Lossy محسوب میشود و برای همین شرط RMSE داشت. فقط Candidateای پذیرفته میشد که مقدار Normalized RMSE از Guard پروژه یعنی 0.012 بیشتر نباشد.
Preflight ابزار
OxiPNG در زمان اجرای Script نصب نمیشد. Dependency از قبل در یک مسیر ثابت قرار داشت و Optimizer فقط وجود و نسخه مورد انتظار را Preflight میکرد. این تصمیم باعث میشد Run پردازشی از Download یا Installer جدا بماند.
& "D:\AI\#tools\oxipng\oxipng.exe" --versionنسخه ثبتشده در پروژه:
oxipng 10.1.1تنظیمات نهایی
برای همین Corpus یک پروفایل تهاجمی انتخاب شد. این اعداد حاصل تصمیم همین پروژه هستند و نباید بدون Benchmark روی سایت دیگری کپی شوند. عکس پرتره، Screenshot، نمودار و Illustration ممکن است به تنظیمات متفاوتی نیاز داشته باشند.
# JPEG
$JpegQScale = 8
# WebP
$WebpQuality = 65
# AVIF
$AvifCrf = 40
# OxiPNG
$OxiPngOptimizationLevel = '3'
$OxiPngStripMode = 'safe'
$OxiPngAlphaOptimization = $true
$OxiPngInterlace = 'off'
$OxiPngThreads = 4
$OxiPngTimeoutSec = 5
# PNG Palette
$PngEnablePaletteFallback = $true
$PngPaletteColors = 256
$PngPaletteDither = 'FloydSteinberg'
$PngPaletteMaxNormalizedRmse = 0.012
$PngPaletteCompressionLevel = 9JPEG و WebP
در JPEG مقدار qscale بالاتر به معنی فشردهسازی تهاجمیتر است. در WebP برعکس، Quality پایینتر معمولاً فایل کوچکتری تولید میکند. FFmpeg برای این دو فرمت در Corpus پروژه نتیجه قابل استفادهای داشت. جزئیات کاملتر Encoderها و Syntax را میتوان در آموزش FFmpeg صفر تا صد دنبال کرد.
PNG
PNG مسیر جداگانهای داشت: OxiPNG بهعنوان موتور Lossless و ImageMagick فقط برای Palette fallback. این معماری بهجای تلاش برای یکسانسازی همه Formatها، رفتار هر Format را جدا در نظر گرفت.
چرا این اعداد عمومی نیستند؟
کیفیت مطلوب فقط یک عدد نیست. نوع تصویر، حساسیت بصری، اندازه اولیه، هدف نمایش و هزینه Re-upload روی تصمیم اثر دارند. برای سایت دیگری بهتر است ابتدا یک Sample کوچک و نماینده از Corpus انتخاب شود و چند پروفایل روی همان Sample مقایسه شوند.
Validation و Reporting
یکی از نقاطی که این پروژه را از یک Batch Converter ساده جدا کرد، گزارشگیری ساختاریافته بود. Console برای مشاهده زنده مناسب است، اما برای تحلیل یک Run چندصدفایلی کافی نیست. هر Session باید Evidence قابل آرشیو تولید کند.
_DJH-Image-Optimizer-Logs\
├── optimizer-YYYYMMDD-HHMMSS.log
├── optimizer-files-YYYYMMDD-HHMMSS.csv
├── optimizer-formats-YYYYMMDD-HHMMSS.csv
├── optimizer-no-convert-YYYYMMDD-HHMMSS.csv
└── optimizer-session-YYYYMMDD-HHMMSS.tar.xzValidation فایل
هر Candidate قبل از Replace شدن باید از Validation عبور میکرد. این کار جلوی حالتی را میگرفت که صرفاً به خاطر ایجاد شدن یک فایل کوچکتر، Source سالم با خروجی ناقص یا نامعتبر جایگزین شود.
CSVهای هر Run
گزارش فایلبهفایل شامل Format، Status، Mode، SourceBytes، CandidateBytes، SavedBytes، SavedPercent، RMSE، Backup و Error بود. گزارش مجزا به تفکیک Format نیز کمک کرد مشکل PNG بهجای گم شدن در میانگین کل Corpus سریع دیده شود.
آمار کل Corpus
یکی از اصلاحات مهم Reporting اضافه کردن TOTAL SOURCE SIZE و TOTAL FINAL SIZE بود. این دو شاخص پاسخ میدهند کل مجموعه واقعاً چقدر کوچک شده است؛ در حالی که آمار فایلهای Replaceشده فقط عملکرد Encoder روی زیرمجموعه موفق را نشان میدهد.
باگ v1.7.1
یکی از مهمترین خطاهای پروژه اصلاً در Encode رخ نداد. نسخه v1.7.1 پردازش تمام 860 فایل را کامل کرد، اما هنگام ساخت گزارش نهایی متوقف شد؛ چون متغیر $noConvertRows قبل از مقداردهی Reference شده بود.
Write-Utf8BomCsv -Rows $noConvertRows -Path $NoConvertCsv
The variable '$noConvertRows' cannot be retrieved because it has not been set.Crash بعد از 860 فایل
ظاهر ماجرا میتوانست این تصور را ایجاد کند که کل Run شکست خورده است، اما Stage پردازش تصاویر تمام شده بود. Failure در Reporting رخ داده بود. این تفکیک برای تصمیم بعدی حیاتی بود.
چرا Reprocess نکردیم؟
Restore کردن 860 فایل و اجرای مجدد Encoder فقط برای جبران یک باگ گزارشگیری کار منطقی نبود. Evidence فایلبهفایل موجود بود و تصاویر قبلاً پردازش شده بودند. بنابراین باید Reporting Recover میشد، نه خود تصاویر.
Fix و Recovery
در v1.7.2 ترتیب ساخت متغیر اصلاح شد و یک Recovery Script نیز طراحی شد تا بتوان Report همان Session را از CSVهای موجود بازسازی کرد. این اتفاق نشان داد Reliability یک Pipeline فقط به Encoder محدود نیست؛ Log، Report و Recovery نیز بخشی از تعریف موفقیت هستند.
نتیجه Run نهایی
Session نهایی معتبر با نسخه v1.7.2 و شناسه 20260823-225751 اجرا شد. این Run همان Evidence اصلی است که نتیجه پروژه بر مبنای آن گزارش میشود.
| فرمت | نتیجه | کاهش حجم |
|---|---|---|
| JPG | 581 از 591 Optimize | 27.85% |
| PNG | 246 از 246 Optimize | 9.63% |
| WebP | 23 از 23 Optimize | 14.90% |
Images found : 860
Optimized : 850
No size benefit : 10
Already processed : 0
Failed : 0
TOTAL SOURCE SIZE : 130.52 MB
TOTAL FINAL SIZE : 106.69 MB
TOTAL CORPUS SAVED : 23.82 MB (18.25%)نتیجه هر فرمت
JPG بیشترین Saving درصدی را داشت و 581 فایل از 591 مورد با Candidate کوچکتر جایگزین شدند. هر 23 WebP نیز Benefit داشتند. ده فایل بدون Benefit همگی JPG بودند و چون Candidate کوچکتری نداشتند، Source آنها دستنخورده باقی ماند.
PNG بعد از تغییر موتور
مهمترین تغییر مربوط به PNG بود. در مسیر اولیه فقط 4 مورد از 246 فایل بهبود پیدا کرده بودند؛ در Run نهایی تمام 246 PNG Candidate کوچکتر معتبر داشتند. تحلیل فایلبهفایل نشان داد 241 مورد با OXIPNG_LOSSLESS و پنج مورد با PALETTE_256 برنده شدند. برای Candidateهای Lossless مقدار RMSE صفر بود و Candidateهای Palette نیز زیر Guard تعیینشده پروژه باقی ماندند.
این نتیجه دلیل خوبی برای یک اصل عمومی است: اگر یک Format در Corpus شما رفتار نامناسبی دارد، قبل از افزایش بینهایت پارامترهای همان Encoder بررسی کنید آیا Engine مناسب آن Format را انتخاب کردهاید یا نه.
آیا هر Saving ارزش دارد؟
موفق بودن Optimization لزوماً به معنی ارزشمند بودن Re-upload نیست. اگر یک فایل فقط چند درصد کوچک شده باشد، زمان انتقال، کنترل، جایگزینی و Validation آن ممکن است بیشتر از منفعت واقعی باشد. برای این Batch تصمیم پروژه این بود که Saving کمتر از 9 درصد ارزش Re-upload ندارد.
آستانه 9 درصد
عدد 9 درصد یک Best Practice عمومی نیست. این Threshold برای هزینه و شرایط همین پروژه انتخاب شد. در پروژهای دیگر ممکن است 3 درصد برای فایل چندمگابایتی مهم باشد یا حتی 15 درصد برای یک فایل بسیار کوچک ارزش عملیات نداشته باشد.
نتیجه Prune
با فیلتر SavedPercent < 9.00 تعداد 205 فایل در گروه کمارزش قرار گرفتند.
| فرمت | تعداد | وضعیت |
|---|---|---|
| JPG | 38 | زیر آستانه |
| PNG | 164 | زیر آستانه |
| WebP | 3 | زیر آستانه |
اسکریپت Prune تعداد 205 فایل فعلی و 195 Backup متناظر را حذف کرد و هیچ Failure نداشت. برای 10 فایل، Original متناظر وجود نداشت؛ این همان 10 مورد NO_BENEFIT بود که از ابتدا Replace نشده بودند و طبیعتاً Backup نیز برایشان ساخته نشده بود.
Targets : 205
Deleted current : 205
Deleted originals : 195
Missing current : 0
Missing originals : 10
Failed : 0مسیر عملی برای سایت دیگر
اگر بخواهیم نتیجه این پروژه را به یک Workflow قابل استفاده برای سایت دیگری تبدیل کنیم، نباید مستقیماً qscale، Quality یا Threshold این پروژه را کپی کنیم. بخش قابل تعمیم، روش تصمیمگیری است.
پیشنهاد عملی این است که قبل از پردازش کل آرشیو، تصاویر را بر اساس Format و نوع محتوای بصری گروهبندی کنید. برای JPEG یک پروفایل Lossy آزمایشی، برای PNG یک مسیر Lossless و در صورت نیاز Palette، و برای WebP/AVIF تنظیمات جدا در نظر بگیرید. نتیجه هر گروه را مستقل اندازه بگیرید و فقط بعد از آن سراغ Full Run بروید.
قبل از اجرا
- از کل
wp-content/uploadsیا مجموعه هدف Inventory بگیرید و تعداد فایلها، فرمتها و حجم کل را ثبت کنید. - Backup جدا و قابل بازیابی داشته باشید؛ Backup داخل همان Pipeline جای Backup مستقل سایت را نمیگیرد.
- یک Sample کوچک ولی نماینده از JPG، PNG، WebP و سایر فرمتهای موجود انتخاب کنید.
- ابعاد، Extension، Naming و Metadataهایی را که باید حفظ شوند قبل از Run مشخص کنید.
- ابزارها و نسخههای مورد استفاده را Preflight کنید.
هنگام پردازش
- برای هر Format یک Strategy مناسب انتخاب کنید.
- Candidate را جدا از Source بسازید.
- Candidate را Validate کنید.
- حجم و در صورت Lossy بودن، کیفیت را مقایسه کنید.
- فقط خروجی بهتر را جایگزین کنید.
- نتیجه هر فایل و Summary هر Format را ذخیره کنید.
- در Retestها ابتدا Originalها را Restore کنید.
قبل از Upload
- به حجم کل Corpus قبل و بعد نگاه کنید، نه فقط Saving فایلهای موفق.
- Threshold اقتصادی یا عملی Re-upload را برای پروژه خودتان تعیین کنید.
- ساختار مسیرها و نام فایلها را دوباره Scan کنید.
- چند تصویر Sample را بصری بررسی کنید.
- بعد از جایگزینی روی سایت، صفحات مهم، Responsive images و Thumbnailها را Validation کنید.
- Backupهای موقت را فقط زمانی حذف کنید که دیگر Rollback لازم نباشد.
اگر پروژه شما علاوه بر تصاویر، آرشیوهای رسانهای بزرگ دیگری دارد، مقاله پروژه عملی کاهش حجم آرشیو موسیقی با FFmpeg نمونه دیگری از همین فلسفه اندازهگیری، Batch Processing و تصمیم بر اساس نتیجه واقعی است.
خطاها و وضعیت نهایی
جدول زیر مهمترین مشکلاتی را که در طول پروژه ثبت شدند کنار آخرین وضعیت آنها قرار میدهد. این بخش عمداً Failureها را حذف نمیکند، چون بخش مهمی از ارزش یک پروژه عملی دانستن مسیرهایی است که نتیجه مطلوب ندادهاند.
| مشکل | راهحل | وضعیت |
|---|---|---|
| PNG با FFmpeg سود ندارد | OxiPNG primary | Validated |
| ImageMagick brute-force کند است | Palette fallback محدود | Validated |
| PNG32 فایل را بزرگ میکند | حذف forcing | Resolved |
| Reporting v1.7.1 Crash | v1.7.2 + Recovery | Validated |
| فایلهای زیر 9% | Prune جداگانه | Validated |
| Cleanup عمومی Original | ابزار با تأیید YES | Not verified |
Candidate بزرگتر است
این Error نیست. Source از قبل ممکن است بهتر Compress شده باشد. Candidate بزرگتر باید حذف شود و Source بدون تغییر بماند.
فایل original وجود ندارد
اگر فایل هرگز Replace نشده باشد، Backup با نام .original.* نیز ساخته نمیشود. در Run Prune، ده Original مفقود دقیقاً با ده فایل No-benefit تطابق داشتند.
چه چیزهایی هنوز تأیید نشده؟
آخرین Evidence پروژه اجرای موفق Optimizer v1.7.2 و حذف موفق 205 فایل زیر آستانه 9 درصد را تأیید میکند. ابزار Generic برای حذف باقیمانده فایلهای *.original.* ساخته شده، اما Log اجرای آن در مستندات موجود نیست. به همین دلیل نمیتوان ادعا کرد Cleanup نهایی انجام شده است.
همچنین تعداد 655 فایل فعلی از تفریق 205 فایل حذفشده از 860 فایل محاسبه میشود، اما Scan مستقل بعد از Prune در Evidence موجود ثبت نشده است. بنابراین 655 «وضعیت مورد انتظار» است، نه Count مستقیم مشاهدهشده بعد از آخرین عملیات.
سوالات متداول
پرسشهای زیر روی مهمترین تصمیمها و ابهامهایی تمرکز دارند که در پروژههای واقعی بهینهسازی تصاویر WordPress پیش میآیند.
آیا برای کاهش حجم تصاویر وردپرس حتماً باید از افزونه استفاده کنیم؟
خیر. افزونه برای بسیاری از سایتها مناسب است، اما در پروژههای Migration یا Batch میتوان تصاویر را خارج از WordPress نیز Optimize کرد؛ به شرط داشتن Backup، Validation و مسیر بازگشت امن.
آیا این پروژه ابعاد تصاویر را تغییر داد؟
خیر. Resize در Scope این پروژه نبود و ابعاد، نام، مسیر و Extension نهایی فایلها حفظ شدند.
چرا FFmpeg برای PNGهای این پروژه مناسب نبود؟
در Run اولیه فقط 4 مورد از 246 PNG Candidate کوچکتر تولید کردند. همین Evidence باعث شد مسیر PNG از FFmpeg جدا و OxiPNG به موتور اصلی تبدیل شود.
OxiPNG کیفیت PNG را کاهش میدهد؟
مسیر اصلی OxiPNG در این پروژه Lossless بود. کاهش احتمالی کیفیت فقط در Palette fallback مطرح میشد و آن مسیر با RMSE Guard کنترل شد.
چرا ImageMagick بهتنهایی استفاده نشد؟
ImageMagick قابلیت لازم را داشت، اما معماری brute-force چندمرحلهای برای این Batch بیش از حد کند بود. در نسخه نهایی فقط برای Palette fallback و بررسی اختلاف تصویری استفاده شد.
چرا فایل Original نگه داشته شد؟
برای Rollback، Retest و مقایسه معتبر. بدون Original، اجرای Engine جدید ممکن بود روی فایل قبلاً Re-encodeشده انجام شود و Benchmark را منحرف کند.
اگر Candidate از فایل اصلی بزرگتر شود چه اتفاقی میافتد؟
Source حفظ میشود و Candidate پذیرفته نمیشود. بزرگتر شدن خروجی لزوماً Failure نیست و ممکن است نشان دهد فایل اصلی از قبل خوب فشرده شده است.
آیا کاهش 18.25 درصد برای هر سایت وردپرسی قابل انتظار است؟
خیر. این عدد مربوط به همین Corpus، همین تنظیمات و همین ترکیب فرمتهاست. سایت دیگر ممکن است Saving بسیار کمتر یا بیشتری داشته باشد.
چرا فایلهای زیر 9 درصد صرفهجویی حذف شدند؟
این یک تصمیم عملی برای همین پروژه بود، چون Saving کمتر از 9 درصد در برابر هزینه Re-upload ارزش کافی نداشت. این Threshold یک استاندارد عمومی نیست.
WebP یا AVIF بهتر از حفظ JPG و PNG نبود؟
ممکن بود در پروژهای دیگر انتخاب مناسبی باشد، اما Requirement این پروژه حفظ Extension اصلی بود. Format conversion یک معماری متفاوت است و باید جداگانه از نظر سازگاری و Workflow سایت بررسی شود.
آیا Cleanup نهایی فایلهای .original.* انجام شد؟
ابزار Cleanup ساخته شد، اما در Evidence موجود Log اجرای نهایی آن ثبت نشده است. بنابراین وضعیت آن باید Not Verified در نظر گرفته شود.
نتیجهگیری
این پروژه با یک فرض ساده شروع شد: تصاویر یک سایت وردپرس قدیمی را با یک Pipeline عمومی فشرده کنیم. داده واقعی خیلی زود نشان داد این فرض کافی نیست. FFmpeg برای JPG و WebP خوب عمل کرد، اما برای 242 مورد از 246 PNG هیچ Benefit نداشت. ImageMagick انعطاف بیشتری ایجاد کرد، ولی معماری brute-force هزینه پردازشی نامناسبی داشت. در نهایت PNG به OxiPNG سپرده شد و ImageMagick فقط در جایی باقی ماند که ارزش اضافه ایجاد میکرد.

نتیجه Run نهایی روی 860 تصویر، 850 Optimize، 10 No-benefit و صفر Failure بود و حجم کل Corpus 23.82 مگابایت یا 18.25 درصد کاهش پیدا کرد. اما شاید مهمترین نتیجه این باشد که «Encode موفق» آخر Workflow نیست. Candidate باید Validate شود، Original باید قابل Restore باشد، گزارش باید قابل اعتماد باشد و حتی بعد از Compression باید تصمیم گرفت کدام Saving واقعاً ارزش Re-upload دارد.
اگر بخواهم یک قانون از این پروژه باقی بگذارم، این است: **ابزار را بر اساس شهرت یا عادت انتخاب نکنید؛ Corpus را اندازه بگیرید و اجازه دهید Evidence معماری را تعیین کند.**


