در PNG متحرک، کدام تصویر واقعاً فریم اول است؟

برای گزارش از یک فایل PNG متحرک تصویر گرفته‌اید و می‌خواهید زیرش بنویسید «فریم اول». قبل از نوشتن این زیرنویس، ببینید تصویر شمارهٔ صفر چه نقشی در فایل دارد. گاهی همان تصویری است که حرکت با آن شروع می‌شود؛ گاهی فقط یک تصویر ثابت است برای برنامه‌ای که انیمیشن را پخش نمی‌کند. در حالت دوم حتی اگر تصویر ثابت را ببینید، نباید آن را جزو فریم‌های پخش بشمارید.

یک کارت کهربایی با نقش هلال، جدا از دو کارت روشن با دایره‌های سبزآبی و آلویی
تصویرسازی با هوش مصنوعی: کارت جدا نقش تصویر ثابت را دارد. خودِ فایل‌های تمرین فقط شامل رنگ‌های یکدست‌اند.

تمرین: دو بار رنگ اول را پیدا کنید

برای این مثال دو فایل ساختگی ۸ در ۸ پیکسل ساختیم. هردو ابتدا رنگ سبزآبی و بعد رنگ آلو‌یی را پخش می‌کنند. به یکی از فایل‌ها یک تصویر ثابتِ کهربایی هم اضافه کردیم. فایل‌ها را با نسخهٔ ۱۲٫۳٫۰ کتابخانهٔ Pillow دوباره باز کردیم و رنگ پیکسل وسط هر تصویر را خواندیم.

نام فایل نمونهتصویر با اندیس صفراولین فریم پخشدومین فریم پخش
poster-case.pngکهربایی، فقط برای نمایش ثابتسبزآبی، اندیس ۱آلویی، اندیس ۲
included-case.pngسبزآبی، جزو پخشسبزآبی، اندیس ۰آلویی، اندیس ۱

در فایل اول سه تصویر برای خواندن داریم و فقط دو فریم برای پخش. در فایل دوم اصلاً تصویر سومی وجود ندارد. حالا اگر خواننده «فریم دوم» را بخواهد، در هر دو مورد منظورش تصویر آلویی است، اما برای پیدا کردنش باید دو اندیس متفاوت به برنامه بدهید. همین تفاوت کوچک کافی است تا زیرنویسِ عکسی که تحویل می‌دهید اشتباه از آب دربیاید.

به‌جای حدس از ظاهر تصویر، نقش آن را از فایل بخوانید

در رایانه‌ای که پایتون و Pillow دارد، کد زیر را با نام inventory.py ذخیره کنید. فرمان python inventory.py poster-case.png را با نام نسخهٔ محلیِ فایل خود اجرا کنید. خروجی، اول شمار فریم‌ها در یک دور پخش را می‌دهد. بعد کنار هر اندیس، نقش همان تصویر را می‌نویسد: fallback یعنی تصویر ثابت جداست؛ play-1 یعنی حرکت از این تصویر شروع می‌شود. این کد چیزی در فایل اصلی نمی‌نویسد.

from PIL import Image
import sys

with Image.open(sys.argv[1]) as im:
    if im.get_format_mimetype() != "image/apng":
        raise SystemExit("Expected an animated PNG")
    start = int(bool(im.info.get("default_image", False)))
    print("playback frames:", im.n_frames - start)
    for index in range(im.n_frames):
        im.seek(index)
        role = "fallback" if index < start else f"play-{index-start+1}"
        print(index, role, im.size)

دلیل این تفاوت در ساختار فرمت است. طبق استاندارد PNG کنسرسیوم وب، اگر قطعهٔ کنترل فریم با نام fcTL پیش از اولین قطعهٔ دادهٔ تصویر یا IDAT بیاید، تصویر پیش‌فرض در پخش شرکت دارد. اگر کنترل نخست بعد از آن بیاید، تصویر پیش‌فرض جداست. راهنمای APNG در Pillow توضیح می‌دهد که در حالت دوم، شمار تصویرهای قابل خواندن یکی بیش از شمار فریم‌های پخش است. لازم نیست از روی پسوند فایل یا تصویر کوچکی که در پوشه دیده‌اید حدس بزنید.

از خروجی برنامه تا زیرنویس قابل استناد

در جدول گزارش دو ستون جدا بگذارید: «اندیس در فایل، از صفر» و «شماره در پخش، از یک». برای تصویر ثابتِ مستقل، ستون دوم را خالی بگذارید. اگر از تصاویر یک نوار تصویری می‌سازید، فقط ردیف‌های پخش را پشت سر هم بچینید؛ تصویر ثابت را در صورت نیاز جدا نشان دهید. Pillow هنگام خواندن هر فریم، تصویر ترکیب‌شده و آمادهٔ نمایش را می‌دهد؛ این تصویر لزوماً همان دادهٔ فشردهٔ یک قطعه درون فایل نیست.

فایل اصلی، هش SHA-256 آن، نسخهٔ کتابخانه و جدول شماره‌ها را کنار هم نگه دارید. آن‌وقت می‌توانید دقیق بنویسید «فریم اول پخش در این نسخهٔ فایل». اگر ادعا این است که یک پیام‌رسان فلان تصویر را به مخاطب نشان داده، برای آن ادعا شاهد جداگانه‌ای از همان محیط لازم دارید.

نویسنده: OSINT Jet · مرور منابع اولیه: ۷ نوامبر ۲۰۲۶