Skrypt w fish, kompresujący 130 tysięcy zdjęć w ImageOptim automatycznie (cz. I)

25/05/2017, 10:44 · · · 9

Wczoraj pisałem o instalacji fisha zamiast basha, a był to wstępny krok do małego projektu, który nadal robię. Otóż wszystko rozbija się o to, że zdjęcia i grafiki na serwerze iMagazine, zbierane od wielu lat, zajmują 28 GB przestrzeni na dysku. To nie tyle problem dla nas, ale raczej dla Was wszystkich – im więcej dany materiał waży, tym więcej danych trzeba pobrać i tym dłużej się strona ładuje. Znalazłem w końcu czas na optymalizację.

ImageOptim

Pisałem o tym programie oraz o moich nastawach blisko dwa lata temu – korzystam z niego do dzisiaj. Problem w tym, że program znakomicie spisuje się do kompresji niewielkiej ilości zdjęć, ale przy większych ilościach (1000+), zaczyna wariować.

Kompresja ręczna

Po zgraniu 28 GB zdjęć i grafik z serwera, rozpocząłem ich kompresję, miesiąc po miesiącu (są podzielona na foldery według schematu .../RRRR/MM/). Na początku nie było to problemem, bo w 2012 roku mieliśmy miesięcznie kilkaset plików do kompresji, przeważnie były to JPG, a ich kompresja trwała kilka sekund. PNG niestety kompresują się prawdopodobnie od 20- do 40-krotnie wolniej. Najważniejsze jednak, że ImageOptim potrafi wykorzystać wszystkie rdzenie i wątki, więc załadowanie wielu plików na raz to spory bonus – kompresowanie pojedynczych plików wydłuża czas mniej więcej czterokrotnie.

Problem zaczął się w późniejszych latach, gdy jeden folder zawierał dwa tysiące lub więcej plików. Większość ImageOptim potrafił przetworzyć, ale na niektórych plikach, bez wyraźnej przyczyny, po prostu się wykrzaczał. Wkurzyłem się i doszedł do tego fakt, że traciłem na tym za dużo czasu. Od początku wiedziałem, że powinienem to zautomatyzować, ale nie miałem kiedy się tym zająć.

Automatyzacja

Wczoraj w końcu usiadłem do napisania skryptu. Początkowo chciałem to zrobić w bashu, ale jak już wspominałem wczoraj, skoro musiałem przypominać sobie syntax, to stwierdziłem, że przesiądę się od razu na fisha.

Zacząłem od następującego skryptu:

#!/usr/local/bin/fish
set -e imagefilelist
echo Variables cleaned up.
set imagefilelist (find /Volumes/Macintosh\ HDD/!\ iMag\ Temp\ !/2014/ -type f)
echo File paths loaded.
echo Files found: (count $imagefilelist)
set imagefilelistcounter (count $imagefilelist)
for i in (seq $imagefilelistcounter)
    /Applications/ImageOptim.app/Contents/MacOS/ImageOptim $imagefilelist[$i]
    echo Current file: $imagefilelist[$i]
    echo Files remaining: (math "$imagefilelistcounter-$i")
end
set -e imagefilelist
set -e imagefilelistcounter
set -e i

Wytłumaczenie poszczególnych linii:

  1. Definicja shella, który ma uruchomić skrypt.
  2. Wyzerowanie zmiennej imagefilelist, która zawiera listę wszystkich plików do skompresowania – robi to argument -e. Nie do końca wiem jeszcze jak fish traktuje zmienne, więc wolałem uniknąć problemów.
  3. Wyświetlenie informacji o wyczyszczeniu zmiennych.
  4. Ustawiam zmienną imagefilelist i wywołuję komendę find, której wynik będzie przypisany do zmiennej (dlatego jest w nawiasie). Komenda find wywołuje się pierwsza – jej wynikiem jest tekstowa lista plików wraz ze ścieżkami do nich – a jej wynik zostaje zapisany do imagefilelist. Ścieżka do plików jest ustawiona na sztywno – powinienem ją dodać jak argument przy wywoływaniu skryptu, ale nie zrobiłem tego, bo piszę go dla siebie i prawdopodobnie wykorzystam go tylko raz.
  5. Info dla mnie.
  6. Info dla mnie – wyświetla wynik komendy count $imagefilelist, która liczy ile ścieżek do plików zapisało się w zmiennej imagefilelist.
  7. Deklaruję kolejną zmienną imagefilelistcounter, która zawiera liczbę ścieżek do plików zapisanych do zmiennej imagefilelist. Tak, powinien był zrobić zamienić to miejscami z linią 6, aby wykorzystać zmienną i nie liczyć tego dwa razy.
  8. Tutaj rozpoczyna się właściwe działanie na plikach. Skorzystałem ze zmiennej for, która wykona komendę…
  9. /Applications/ImageOptim.app/Contents/MacOS/ImageOptim $imagefilelist[$i] dla każdej ścieżki. W skrócie zrobi tak: otworzy ImageOptim z pierwszym plikiem zapisanym do zmiennej imagefilelist, skompresuje ten plik, ImageOptim się automatycznie zamknie po skompresowaniu pliku, a pętla zrobi to samo dla drugiego, trzeciego i kolejnych plików, aż dojedzie do końca listy.
  10. Po kompresji każdego pliku, skrypt wyświetli w terminalu ścieżkę kompresowanego pliku…
  11. … oraz policzy ile plików jeszcze pozostało do skompresowania i również tę wartość wyświetli.
  12. Kończę tutaj pętlę.
  13. Na koniec pozostaje wyzerowanie zmiennych. Tutaj też powinienem był przenieść zerowanie zmiennej z linii 2 skryptu.

W praktyce skrypt działa wzorowo. Jest prosty i robi co do niego należy, ale w rzeczywistości jest bardzo nieefektywny – jak wspominałem powyżej, ImageOptim nie wykorzystuje swojego pełnego potencjału na pojedynczych plikach i warto mu zapodać ich więcej za jednym razem.

Zamiast optymalizować ten skrypt, napisałem kolejny… ale o tym napiszę w następnym wpisie z serii.



9

Wojtek Pietrusiewicz

Wydawca, fotograf, podróżnik, podcaster – niekoniecznie w tej kolejności. Lubię espresso, mechaniczne zegarki, mechaniczne klawiatury i zwinne samochody.