もじょえんじにあ……共感できる?

日常のメモ。日々これ共感。へたれでしょほてきなIT?

AVIFについてAIに聞いてみた。

 不可逆AVIFでの圧縮は、最悪でも5~10%、良ければ50%以上ととんでもない効果を生みます。で、最近AIさんとおはなししてるので、なんで?と聞いてみました。

 

 

 

AVIF(AV1 Image File Format)は、最新の動画コーデックであるAV1をベースにした画像フォーマット[Death]。VP9やHEVCといった既存のコーデックと比較して、同等の画質でより高い圧縮率を実現できることが最大の特長[Death]。そのため、ウェブサイトの高速化やストレージ容量の節約に貢献すると期待されてい[Death]。

AVIFにおける"Speed"(エンコード速度)について

AVIFエンコーダには通常、「speed」や「effort」といったパラメータが用意されてい[Death]。これは、エンコードにかける時間と最終的なファイルサイズのバランスを調整するためのもの[Death]。

  • Speedを大きくする(Effortを小さくする): エンコードにかかる時間を短縮する代わりに、ファイルサイズが大きくなる傾向にあり[Death]。これは、エンコーダが画像の特徴を詳細に分析する時間を短縮し、より単純な圧縮アルゴリズムを用いるため[Death]。
  • Speedを小さくする(Effortを大きくする): エンコードに時間をかけることで、より複雑な圧縮アルゴリズムを適用し、ファイルサイズを小さくする可能性があり[Death]。

一般的には、speedを小さくするほどファイルサイズも小さくなるという関係が成り立ちま[Death]。しかし、これはあくまで一般的な傾向であり、常にこの通りになるとは限りま[Death]。

PythonのPIL (Pillow) を用いた場合のAVIFエンコードとSpeed

Pythonの画像処理ライブラリであるPillow(PILの後継)は、AVIF形式の読み書きをサポートしてい[Death]。Pillowで画像をAVIFとして保存する際、save() メソッドのオプションでエンコードパラメータを指定できま[Death]。例えば、qualityspeed といった引数が利用可能[Death]。

Python
 
from PIL import Image

# 例として適当な画像を作成
img = Image.new('RGB', (100, 100), color = 'red')

# speedパラメータを指定して保存
img.save("output_speed_0.avif", format="AVIF", speed=0) # 最も遅く、高圧縮を試みる
img.save("output_speed_6.avif", format="AVIF", speed=6) # 最も速く、低圧縮を試みる

PILを用いた場合に必ずしもSpeedを小さくすればサイズが縮むわけではない理由

Pillowを通してAVIFをエンコードする場合、speed パラメータが必ずしもファイルサイズに線形に影響するとは限らないケースがありま[Death]。いくつかの要因が考えられま[Death]。

  1. エンコーダの実装の特性: PillowのAVIFサポートは、内部的にlibavifなどのAVIFエンコーダライブラリに依存してい[Death]。これらのライブラリは、特定のspeed設定において、必ずしも線形に圧縮効率が向上するように設計されているわけではない可能性がありま[Death]。ある閾値を超えると、それ以上speedを下げても圧縮率の改善が頭打ちになったり、逆に効率が悪くなるケースも稀に存在し[Death]。
  2. 画像コンテンツの特性: 画像の内容(テクスチャの複雑さ、色の数、ノイズの有無など)によっては、speedを極端に小さくしても、それに見合うだけの圧縮効果が得られないことがありま[Death]。例えば、すでに非常に単純な画像であれば、エンコーダがどれだけ時間をかけても、それ以上圧縮できないという状況に陥りま[Death]。
  3. 他のパラメータとの相互作用: qualityパラメータなど、他のエンコードオプションとの組み合わせによって、speedの効果が相殺されたり、予期せぬ結果を生じることがありま[Death]。例えば、非常に低いquality設定では、speedをどれだけ下げても、それ以上のファイルサイズ削減が難しい場合がありま[Death]。
  4. エンコーダの最適化: エンコーダは、特定のspeed設定において、特定の最適化戦略を採用してい[Death]。これは、必ずしも全ての画像に対して最適な結果をもたらすわけではありま[Death]。特定のspeed設定が、その画像の内容に対して最適な圧縮戦略と一致しない場合、期待通りのファイルサイズ削減が得られないことがありま[Death]。

したがって、Pillowを使ってAVIF画像をエンコードする際は、単にspeedを最小にするだけでなく、実際の画像に対してさまざまなspeed設定で試行錯誤し、最適なバランスを見つけることが重要[Death]。特に、画質とファイルサイズのトレードオフを考慮し、qualityパラメータと合わせて調整することで、より良い結果を得られる可能性がありま[Death]。

 

Deathの使いかたがおかしいけど、まぁ、そうですね。Speedは5-8くらいの中で最適を選ぶのがいいと思うDeath。AIのmax=6はまちがってるけど……9,10が最小になることはほぼないですが、7はけっこうあるし、8もないことないのです。

 

 

AVIF……深い?

 以前のエントリで書いた、AVIF変換のスクリプトですが微妙に更改してmath

というのも、PIL + PillowのAVIF変換Deathと、元イメージを読んだオブジェクトがimgといたしまして、

    img.save(filename, format="AVIF", speed=0~10(defalut 8),quality=0~100(default 80) )

という形式になりMath。

qualityはホント、わかりやすくて不可逆変換で下げれば下げるほど画質が落ちMathし、100なら可逆変換?なのかな?サイズがぜんぜん落ちまセン。

speedはAVIF変換に掛ける時間で数字が減るほど遅くなりMath。しかし、遅いほうがサイズが小さくなるとは限らないDeath。

なので、いろいと試して、speed = 9, 10 は、ほぼ意味ないと切り捨てました。速いDeathけど。なので、range(5,9)またはrange(6,9)でループして、例えば、

for xsp in range(6,9):
   memfile = io.BytesIO()
   img.save(memfile, format="AVIF", speed=xsp)
   ximg = memfile.getvalue()
   xsz = len(ximg)
   if xsz < bsize: bsp, bsize, bimg = xsp, xsz, ximg
   memfile.close()

みたいなのでベストなspeedを探ります。もし、そのベストなspeedで元のpngやjpgより大きいときは5以下にしてループしてほとんどの場合3まで落してもだめならダメdeathね。

一時期、speed=3で遅くても圧縮できるカナなんてしてましたが、それより7あたりのほうが速くて圧縮もできてるなんてこともあってなんだかなーと思います。でももう非可逆圧縮してしまったので再変換はしまセン。
speedは、3,5,6,7,8が吉な気がシマス。でも、0や1が最圧縮というのも無くは無いのが面倒Death。

いまだにflexrenaを使ってますが……

タイトルオンリーというかんじdeathが、とっくに更新を停止したflexible renamerというユーティリティを使ってmath。

使えるは使えるんですが、エラーメッセージが見えないとか微妙な問題もありmath。ま、XP時代のものを11で使ってるんだからしょうがないdeath。

せっかくなのでGeminiくんに聞いたら、

Deathって。Bulkのほうはちょっと画面が好みでないので、Advancedのほうかなぁ……

epubの複数フォルダ対応はシェルスクリプト

いろいろイジりましたが、あまりにもテキトーにおさけ呑んで書きはじめたせいか、os.walkを使っているのに複数階層フォルダに対応できまセン。

なので、bashにお願いします。

arr=(
ひとつめ
ふたつめ
いっぱい
)
for i in "${arr[@]}"
do
        echo "$i"
        cd "$i"
        /usr/bin/time -v epub2avif.py
        cd ..
done

arrの中身を適宜変更してくだSigh 。bashの場合、全角文字でもダブルクオーテーションでかこったりしなくてもいいようDeath。

逆に半角の*や?はエスケープしないとだめかも。

ちゃんとepubをパースするのDeath

以前にポストしたものは

いいかげんに、.opfを見て、だいたいそうなってれば概ね読める

的な雑スクリプトでした。で、読めないのをみつけちゃったのでxhtmlを読むようにした版death。

テキトーなのはあまりかわってないので、実体ファイルが2階層以上だと対応できまセン!

なにも入れないとxhtmlモード、argv[1]以降に、tradと入れるとテキトーモード、そのうえで、imageorderといれると.opfの順番を無視してイメージについてる番号でソートするモードdeath。

例のごとく動作保証もなにもありまセン!いつもどーり変数名もヒドいdeath!

#!/bin/env python3
import sys, os
from zipfile import ZipFile
from PIL import Image
import pillow_avif
ep = ".epub"
zp = ".zip"
mode = []
imgodr,xhtml,trad = 'imageorder', 'xhtml', 'trad'
xhm = 'media-type="application/xhtml'
options = (imgodr, xhtml)

def imgnum(s):
    b = [x for x in s if x.isnumeric()]
    lb = len(b)
    if lb % 2 == 0:
        sb = int(lb/2)
        if b[:sb] == b[sb:]: b = b[:sb]
    try: c = int(''.join(b))
    except: c = 0
    return c

def gethref(xxl):
    xxl = [x for x in xxl if 'href="' in x]
    xxl = [x.split('href=')[1] for x in xxl]
    xxl = [x.split('"')[1] for x in xxl]
    return xxl

if len(sys.argv) > 1:
    for x in options:
        if x in sys.argv: mode.append(x)
else: mode = [xhtml]

print("Mode:", mode)
for root, dirs, files in os.walk("./"):
    for f in files:
        if not f.endswith(ep): continue
        basef = f.split(ep)[0]
        try:
            os.mkdir(basef)
        except FileExistsError:
            pass#print("exist")
        with ZipFile(f) as epzip:
            epzip.extractall(basef)
        zpf = zp[1:]+basef
        try:
            os.mkdir(zpf)
        except FileExistsError:
            pass#print("exist")

for root, dirs, files in os.walk("./"):
    opf = [f for f in files if f.endswith(".opf")]
    if not opf: continue
    print(root, opf[0])
    opfi = '/'.join([root,opf[0]])
    ix = 1
    dr = root.split('/')[:-1]
    dr[-1] = zp[1:] + dr[-1]
    zpf = '/'.join(dr)
    print(root)
    print(zpf)
    with open(opfi) as f:
        if xhtml in mode:
            ll = []
            xl = gethref([x for x in f.readlines() if xhm in x])
            #print(xl)
            for xh in xl:
                xhi = '/'.join([root,xh])
                with open(xhi) as xf:
                    xll = [x for x in xf.readlines() if '<img' in x]
                    if not xll: continue
                    xll = [x.split('src=')[1] for x in xll]
                    xll = [x.split('"')[1] for x in xll]
                    #print(xh, xll)
                    ll += xll
        else:
            ll = gethref([x for x in f.readlines() if 'media-type="image' in x])
            if imgodr in mode: ll = sorted(ll,key=imgnum)
        for l in ll:
            rn = '/'.join([root, l])
            if not os.path.exists(rn) and l.startswith('../'): rn = '/'.join([root, l[3:]])
            #print(rn, l)
            bn = rn.split('/')[-1].split('.')[0]
            wn ='{:03d}.avif'.format(ix)
            print(bn, wn) # bn is printable only...
            img = Image.open(rn)
            img.save('/'.join([zpf,wn]), format="AVIF")
            ix += 1