Ruby po kapkách (19.) – třídění obrázků z kamery

31. července 2009

Tentokrát se budeme věnovat důkladnému ohlédnutím za příkladem z minulého dílu a v návaznosti mnoha dalším dříve nabytým znalostem. Měli jsme za úkol vytvořit program, který projde adresář s mnoha obrázky nahranými IP kamerou a všechny obrázky starší než aktuální den přesune do adresářů po jednotlivých dnech. Soubory s obrázky jsou pojmenovány podle vzorce n.jpg, kde n je unikátní číslo. Adresář s obrázky dostane program jako parametr příkazové řádky.

Kód vytvoříme postupně. Pro začátek – než se poustíme do přesouvání souborů – je dobré vytvořit si vnější slupku, která nám třeba pouze vypíše názvy souborů, které nás zajímají, a čas jejich vzniku. Nepředpokládáme, že by se soubory měnily kromě okamžiku, kdy jsou poprvé zapisovány, a proto použijeme hodnotu ctime.

path = ARGV[0] || ‚.‘
dir = Dir.new(path)
dir.grep(/[0-9]+\.jpg/).each do |f|
t = File.stat(File.join(path, f)).ctime
puts „#{f} – #{t}“
end

Když kód uložíme do souboru imgsort.rb a skutečně máme v nějakém adresáři obrázky pojmenované čísly, můžeme program spustit a získat seznam podobný tomu následujícímu.

~$ ruby imgsort.rb images
090725125110001.jpg – Sat Jul 25 12:51:15 +0200 2009
090725125110002.jpg – Sat Jul 25 12:51:15 +0200 2009
090727061331001.jpg – Mon Jul 27 06:13:33 +0200 2009
090727061331002.jpg – Mon Jul 27 06:13:33 +0200 2009

Povšimněte si prvního řádku. Oproti zadání jsme ošetřili situaci, kdy není zadán parametr určující cestu k adresáři. V takovém případě je ARGV[0] nil. Ve výrazu na pravé straně přiřazení do proměnné path se tedy ARGV[0] vyhodnotí jako false a operátor || přistoupí i k vyhodnocení druhé části (pokud je první část operátoru logického nebo true, nemá již smysl vyhodnocovat druhou půlku, protože výsledek musí být tak jako tak true). Hodnotou celého výrazu je pak konstanta s cestou k aktuálnímu adresáři. Bez parametru bude program procházet aktuální adresář. Vybranou cestu používáme k vytvoření instance třídy Dir. Využíváme pak vlastností enumerable kolekcí, abychom získali seznam všech souborů z adresáře. Zajímají nás však jen soubory, jejichž název odpovídá určitému vzorci. Filtraci docílíme pomocí metody grep a vhodného regulárního výrazu. Zbytek je již jasný – snad jen s připomínkou metody File.join, kterou používáme ke korektnímu vytvoření cesty k souboru.

Pojďme nyní udělat další krok. Víme, že potřebujeme zjišťovat, zda soubor je či není z aktuálního dne. A potřebujeme také převádět datum a čas vytvoření obrázku na název adresáře, kam chceme obrázek přesunout. Opravíme také jednu drobnou nedokonalost – ačkoliv filtrujeme názvy a hledáme soubory s koncovkou jpg, mohlo by se stát, že se nám v adresáři objeví podadresář, jehož název bude mít také koncovku jpg. Ten raději necháme bez povšimnutí.

path = ARGV[0] || ‚.‘
today = Time.new.strftime(‚%y%m%d‘)
dir = Dir.new(path)
dir.grep(/[0-9]+\.jpg/).each do |f|
fpath = File.join(path, f)
next if test(?d, fpath)
fday = File.stat(fpath).ctime.strftime(‚%y%m%d‘)
puts „#{f} – #{fday} – #{fday == today}“
end

Pro názvy adresářů použijeme v souladu se zadáním vzor RRMMDD (rok, měsíc a den). Takový řetězec můžeme z objektu třídy Time získat voláním metody srftime s odpovídajícím formátovacím výrazem. V této podobě si připravíme i datum dnešního dne, abychom pak mohli tuto hodnotu porovnávat s hodnotami jednotlivých souborů. Příkazem next uvnitř bloku přeskakujeme iteraci, pokud je náhodou aktuálně zpracovávaný soubor ve skutečnosti adresářem. Pro ověření funkce programu vypisujeme den vzniku souborů a výsledek porovnání s dnešním dnem.

~$ ruby imgsort.rb images
090725125110001.jpg – 090725 – false
090725125110002.jpg – 090725 – false
090730202122005.jpg – 090730 – true
090730202509001.jpg – 090730 – true

Název souboru z kamery je ve skutečnosti generován za použití datumu a času. Je proto vidět, že program pracuje správně (spuštěn 30.7.2009). Blížíme se k finále. Stále ještě bez zapisování na disk si necháme vypsat už konkrétní soubory a operaci, kterou na nich chceme provést.

path = ARGV[0] || ‚.‘
today = Time.new.strftime(‚%y%m%d‘)
dir = Dir.new(path)
dir.grep(/[0-9]+\.jpg/).each do |f|
fpath = File.join(path, f)
next if test(?d, fpath)
fday = File.stat(fpath).ctime.strftime(‚%y%m%d‘)
unless fday == today
daypath = File.join(path, fday)
unless test(?d, daypath)
puts „mkdir #{daypath}“
end
targetpath = File.join(daypath, f)
puts „move #{fpath} to #{targetpath}“
end
end

Upravili jsme vnitřek bloku. Veškerá aktivita se odehrává pouze v případě, že soubor vzniknul jiný den než aktuální, což je ohlídáno podmínkou unless. Uvnitř bloku podmínky vytvoříme cestu k adresáři, kam by se měl obrázek přesunout. tento adresář zatím nemusí existovat. Tuto skutečnost testujeme a v případě, že neexistuje bychom jej vytvořili. Nakonec vytvoříme cílovou cestu pro přenos souboru a vypíšeme odkud kam by přenos probíhal. Výpis na cvičném adresáři vypadá následovně.

~$ ruby imgsort.rb images
mkdir images/090725
move images/090725125110001.jpg to images/090725/090725125110001.jpg

mkdir images/090725
move images/090725125110002.jpg to images/090725/090725125110002.jpg

V tomto výpisu je určitá nesrovnalost. Protože ve skutečnosti nevytváříme adresáře, test na existenci nám opakovaně selhává a program se tváří, že by adresář pro určitý den vytvářel pořád dokola. Tento problém odpoadne s ostrou verzí, kterou dovybavíme voláním metod, které zajistí opravdové vytváření adresářů a přesun souborů.

path = ARGV[0] || ‚.‘
today = Time.new.strftime(‚%y%m%d‘)
dir = Dir.new(path)
dir.grep(/[0-9]+\.jpg/).each do |f|
fpath = File.join(path, f)
next if test(?d, fpath)
fday = File.stat(fpath).ctime.strftime(‚%y%m%d‘)
unless fday == today
daypath = File.join(path, fday)
unless test(?d, daypath)
puts „mkdir #{daypath}“
Dir.mkdir(daypath)
end
targetpath = File.join(daypath, f)
puts „move #{fpath} to #{targetpath}“
File.rename(fpath, targetpath)
end
end

Přibylo volání metod Dir.mkdir a File.rename. Výpis chystané aktivity zůstal zachován, aby bylo možné sledovat,co program dělá. Jak uvidíme, výsledek funguje podle plánu a dělá v obrázcích pořádek.

mkdir images/090725
move images/090725125110001.jpg to images/090725/090725125110001.jpg
move images/090725125110002.jpg to images/090725/090725125110002.jpg

mkdir images/090726
move images/090726050558001.jpg to images/090726/090726050558001.jpg
move images/090726050558002.jpg to images/090726/090726050558002.jpg

mkdir images/090727
move images/090727021319001.jpg to images/090727/090727021319001.jpg
move images/090727021319002.jpg to images/090727/090727021319002.jpg

Při opakovaném spuštění by již program neměl udělat nic (pokud mezitím nenastala půlnoc), protože všechny soubory jsou již přesunuty. Ačkoliv bychom tento kód mohli pod doplnění kontroly na chyby, ke které se již brzy dobereme, nasadit do ostrého provozu, jedná se jen o první násti řešení problému. Řešení, které není z mnoha pohledů optimální. Můžete se proto snažit program vylepšit tak, aby byl:

  • elegantnější a čitelnější (přeorganizovat kód do metod, použít funkcionálního stylu…),
  • rychlejší a efektivnější (neprovádět opakovaně test na existenci adresáře…),
  • kratší (což nejspíš půjde proti předchozím dvěma kritériím).

Ruby je jazyk, který podporuje filozofii „více způsobů, jak udělat jednu věc“ (některé jiné jazyky naopak propagují myšlenku, že pro každou věc je právě jeden nejlepší způsob, jak ji udělat). Na tomto přístupu je sympatické, že se můžete (ovšem nemusíte) opakovaně zabývat předěláváním nějakého řešení tak dlouho, až jste s ním opravdu spokojeni. Je to na vás.

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *