Mastodon
Zdjęcie okładkowe wpisu Skrypt w fish, kompresujący 130 tysięcy zdjęć w ImageOptim automatycznie (cz. I)

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

9
Dodane: 8 lat temu

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.

Wojtek Pietrusiewicz

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

Zapraszamy do dalszej dyskusji na Mastodonie lub Twitterze .

Komentarze: 9

28GB? Poważnie? Pierwszy rok w JPG/PNG, a późniejsze lata w RAWach 😂?

Bardzo fajna i przejrzysta składnia; poważnie zaczynam rozważać przesiadkę na Fisha 😀.

Co do skryptu: faktycznie mogiła! Bardzo wolno to chodzi. Mam nadzieję, że werjsa 2.0 działa sprawniej. Swoją drogą, jeżeli masz taką bogatą bibliotekę do przemielenia polecam rzucić okiem na Squash. Dla testów wrzuciłem mu ok. 1.100 zdjęć z aparatu i nawet nie stęknął.

Dolicz do każdego JPG i PNG po 6-8 plików generowanych przez Retinę i robi się tego sporo.

Naprawdę napisałeś skrypt z tyloma komentarzami, pętlą i innymi niepotrzebnymi rzeczami zamiast wpisać jedną linie w terminalu?

Mozesz uzyc find -exec lub find |xargs

find . -type f -exec /tam/gdzie/mamy/ImageOptim {} ;

Polecam na początek podstawy linuxa, basha i podstawowe polecenia linuxowe i ich argumenty.

Ta linijka jednak nie rozwiązuje dwóch kwestii których nie doczytałeś:
– nauki syntaxu fisha
– otwierania wielu plików z listy jednocześnie

To był wstęp do drugiego skryptu.

Punkt drugi rozwiązać powinna też jedna linijka tylko z wykorzystaniem xargs

find . -type f -print0 |xargs -0 -P4 -n10 /tamgdziejest/ImageOptim

Uruchomi to 4 procesy jednoczesnie i do kazdego wrzuci 10 plikow.

Wiadomo, że nauka jest wazna, ale po co robić coś co już jest dostepne jako podstawowe narzędzia w systamach linuxowych i działa dobrze.

O i to jest ciekawe rozwiązanie – zobaczę potem jak mogę to zaadaptować do swojego! Drugą wersję skryptu, do której ta prowadziła, będę wrzucał za parę godzin – zerknij sobie.

Sprawdź funkcja time co działa szybciej. Może akurat Twój sposób okaże się bardziej skomplikowany ale wydajniejszy :)

Właśnie robię skomplikowany (w sensie dużo „pierdzielenia się”) test wydajności 5 różnych skryptów. Zaraz będą wyniki. Wpis prawie gotowy.