Сортировка изображений по разрешению с помощью Python и PIL

image

Захотелось зарегистрироваться на Хабре, но так как знаниями особо не блещу, а аудитория тут как раз наоборот, решил попробовать выложить скрипт на питоне, который написал по просьбе знакомого за бутылку 7апа :) Мне скриптик так же помог упорядочить свалку картинок ~15гб.

Скрипт проходит по директории и создает в ней папки вида ШиринахВысота и пихает туда соответствующие по разрешению изображения.

P.S. Ошибок не совершает тот, кто ничего не делает.



Copy Source | Copy HTML
  1. #!/usr/bin/env python
  2. # -*- coding: UTF-8 -*-
  3. """ sorts images by resolution"""
  4.  
  5. import os,sys
  6. from PIL import Image
  7.  
  8. # задаёт директорию для сортировки
  9. dirname = os.path.abspath(sys.argv[1])
  10. try:
  11.     newdir = os.path.abspath(sys.argv[2])
  12. except:
  13.     newdir = dirname
  14.  
  15. def image_sort(dirname, newdir, recur= 0):
  16.     if not recur:print 'sorting started ...' # если главная папка
  17.     else: print 'sorting started in %s...'%dirname # если подпапка
  18.  
  19. # собирает все подпапки в список и рекурсивно обходит
  20.  
  21.     imagelist = []
  22.  
  23.     if os.path.isdir(dirname):
  24.         for x in os.listdir(dirname):
  25.             absx = dirname+os.sep+x
  26.             if os.path.isfile(absx):imagelist.append(absx)
  27.             else:
  28.                 #print 'summon subsort in %s'%x
  29.                 image_sort(absx, newdir+os.sep+x,recur=1)
  30.         # проходит по содержимому папки/подпапки
  31.         for name in imagelist:
  32.             try:
  33.                 resolution = Image.open(name).size #получить разрешение
  34.             except IOError:
  35.                 print 'seems not image: '+ name, '/n'
  36.                 continue
  37.             imdir = '%sx%s'%(resolution[ 0],resolution[1])
  38.             imdir = os.path.join(newdir,imdir)
  39.             #если имя папки такое же как и разрешение картинки
  40.             if os.path.split(dirname)[-1] == os.path.split(imdir)[-1]:
  41.                 continue
  42.             elif not os.path.exists(imdir):
  43.                 #print 'making dir %s'%imdir
  44.                 os.mkdir(imdir)
  45.             try:
  46.                 os.system('move "%s" "%s"'%(name,imdir))
  47.             except WindowsError:
  48.                 print 'error with '+ name, '/n'
  49.     if not recur:print 'sorting completed!'
  50.  
  51. if __name__ == '__main__':
  52.     image_sort(dirname, newdir)
+35
23 февраля 2010, 16:11
32
ddsl –1,0

комментарии (34)

НЛО прилетело и опубликовало эту надпись здесь
+16
m1o #
Молодец! Нашёл к чему придраться!
НЛО прилетело и опубликовало эту надпись здесь
+9
ddsl #
Я рад что вы скурпулёзно просмотрели код. :)
+5
ddsl #
*скрупулёзно
+1
muhas #
лучше уж избегать проблем связанных с различными файловыми/операционными системами
к примеру в Windows в имени файла запрещено использование некоторых служебных символов: «\», «/», «:», «*», «?», «"», «<», «>», «|». как с «×» не знаю (да и проверить не на чем)
0
WGH #
это не служебный символ, но проблемы со старыми программами, не дружащими с Юникодом, могут быть
0
bolk #
C «×» всё нормально, на многих файловых системах есть ограничения, они носят исторический характер, но новые Unicode-символы (если файловая система поддерживает Unicode) в эти ограничения не попадают.
0
FkSD #
А есть ли что-то подобное, но только чтоб упорядочить фотографии по дате создания?
0
ddsl #
Сорри, не на тот коммент ответил.
тык
+1
ddsl #
Вот, попробуйте, подправил код выше ( правда сортирует все файлы а не только картинки :) ):

Copy Source | Copy HTML
  1. #!/usr/bin/env python
  2. # -*- coding: UTF-8 -*-
  3. """ sorts images by resolution"""
  4.  
  5.  
  6. import os,sys
  7. import time
  8.  
  9. dirname = os.path.abspath(sys.argv[1])
  10. try:
  11.     newdir = os.path.abspath(sys.argv[2])
  12. except:
  13.     newdir = dirname
  14.  
  15.  
  16. def image_sort(dirname, newdir, recur= 0):
  17.     if not recur:print 'sorting started ...'
  18.     else: print 'sorting started in %s...'%dirname
  19.     if not newdir: newdir = dirname
  20.  
  21.     imagelist = []
  22.  
  23.     if os.path.isdir(dirname):
  24.         for x in os.listdir(dirname):
  25.             absx = dirname+os.sep+x
  26.             if os.path.isfile(absx):imagelist.append(absx)
  27.             else:
  28.                 image_sort(absx, newdir+os.sep+x,recur=1)
  29.         for name in imagelist:
  30.             try:
  31.                 file_date = time.localtime(os.stat(name).st_mtime)
  32.             except EnvironmentError, error:
  33.                 print 'seems error: %s with '%error, name, '/n'
  34.                 continue
  35.             imdir = '%s--%s--%s'%(file_date.tm_year,file_date.tm_mon,file_date.tm_mday)
  36.             imdir = os.path.join(newdir,imdir)
  37.             if os.path.split(dirname)[-1] == os.path.split(imdir)[-1]:
  38.                 continue
  39.             elif not os.path.exists(imdir):
  40.                 print 'making dir %s'%imdir
  41.                 os.mkdir(imdir)
  42.             try:
  43.                 os.system('move "%s" "%s"'%(name,imdir))
  44.             except EnvironmentError:
  45.                 print 'error with '+ name, '/n'
  46.     if not recur:print 'sorting completed!'
  47.  
  48. if __name__ == '__main__':
  49.     image_sort(dirname, newdir)
0
ddsl #
Ещё для пущей крастоты можно заменить строчку

imdir = '%s--%s--%s'%(file_date.tm_year,file_date.tm_mon,file_date.tm_mday)
на
imdir = '%s--%02d--%02d'%(file_date.tm_year,file_date.tm_mon,file_date.tm_mday)
+2
vybe #
Интересная практика — править свой же код в комментариях к своей же статье со своим же кодом:)
А вообще скриптик может быть полезным…
+2
ddsl #
если бы можно было править комменты, было бы намного удобнее.
0
s7ang3r #
Поправьте меня, если я не прав, но может лучше вместо вызова внешней утилиты move использовать функцию move из библиотеки shutil?
0
ddsl #
Да, будет лучше. Я все искал безуспешно эту функцию в модуле os, незнал что её запихали в такие дебри :)
0
krylatij #
Вот здесь есть уже готовая функция для переноса с учетом многих нюансов
code.djangoproject.com/svn/django/trunk/django/core/files/move.py
–2
Ierixon #
дело не в быдло-коде, а в самой логике и ф-ционале
1) сделать выбор (перемещать существующие изображение или только скопировать в сортируемую диру)
2) перемещение файла не на всех ОСь move (mv)
3) опционально сделать выбор между:
а) ложим все изображения рекурсивно по дирам (вид images/sort/10x10; images/sort/subdir/10x20)
б) пихаем со всех субдир в одну сортируемую диру по разрешению изображений (images/sort/10x10, images/sort/10x20)

если это реализовать, будет уже на что то похоже
+1
taliban #
я бы на вашем месте эту пренебрежительно оторванную от сердца фразу про быдлокод убрал бы, ну или хотябы изменил, никто не идеален
0
ddsl #
Надеюсь новый вариант не слишком пафосен? :)
+3
Liksys #
Достаточно чистенький код, но есть пара ньюансов.

Во-первых, я бы советовал использовать вместо указания конкретных исключений (IOError, WindowsError) — Exception. Смысл состоит в том, что все остальные исключения так или иначе охватываются им, так как наследуют его. Кроме того, вы можете получить и текстовое сообщение об этом исключении. По вашему текущему коду, если произошла ошибка, совершенно невозможно узнать, что же случилось. А если возникнет исключение, не описанное в ветке exception, то код просто мирно пойдет дальше, работая с фактически неинициализированными (или инициализированными мусором) переменными.

Во-вторых, старайтесь использовать как можно меньше операции конкатенации (это которая "+") в строках, пользуйтесь операцией подстановки. Так ваш код будет более читабельным, и кроме того, более гибким (не придется переписывать десяток плюсиков при смене строки).

В-третьих, мне не совсем понятно, что вы хотели сказать символами '/n'. Если вы имели в виду символ перевода строки, то он пишется с обратным слешем: '\n'. И в print его использовать не обязательно, эта инструкция автоматически переводит строку за собой.

Вот пример, как лучше писать:
try:
os.system('move "%s" "%s"'%(name,imdir))
except Exception, err:
print 'error with \"%s\": %s' (name, str(err))

Этот код преобразует в читабельный текст информацию о возникшей ошибке, а так же перехватывает все исключения, которые могут образоваться в контексте try.

Использование конкретных исключений делают только в разборе ошибок, когда на определенный тип исключения приходится конкретное, уникальное действие. Например:
try:
some_function()
except Exception1:
exception1_handler()
except Exception2:
exceptuon2_handler()
except:
unknown_exception_handler()
0
Liksys #
Вдогонку, незаметил:
os.system('move "%s" "%s"'%(name,imdir))

Экранируйте внутренние кавычки
+1
ddsl #
Спасибо за указания на недочеты и лишнее \n. Насчет Exception: насколько я знаю то при таком указании получится, что исключения возбуждаемые sys.exit() тоже будут перехватываться, а это вроде как не очень хорошо. А насчет кавычек, не пойму зачем их экранировать ведь они разные.

Поясните эти моменты, если не сложно.
0
stoune #
Ваш вариант обработки исключиний «хороший». Нужно ловить только то что можешь обработать, остальное нужно идти на обработку обработчику верхнего уровня или обработчику по умолчанию если нет такого, который «положит» програму. А совет ловить все исключения в корне неправилен и за такое нужно железной линейкой по пальцам, единственное исключение, можна на самом верхнем уровне поставить обработчик того что уже никто не обработает, который непоготовленому пользователю выдаст «програма упала свяжитесь с поддержкой», а сам прологирует событие и «положит» програму.
0
Liksys #
> исключения возбуждаемые sys.exit() тоже будут перехватываться

Программа у вас не многопоточна, поэтому можно этим не заботиться. Но даже если и так, то перехват исключения от sys.exit() — хорошо, поскольку это сигнал к завершению работы программы. Он прервет выполнение функции, что, в принципе, правильно.
Если хочется обрабатывать такое исключение отдельно, то можно использовать ту конструкцию с несколькими ветками эксцептов, сначала поставить эксцепт на исключение SystemExit, а потом уже на Exception. Но опять же, это надо только при многопоточности.

> А насчет кавычек, не пойму зачем их экранировать ведь они разные.

Ну, это как-бы отчасти правила хорошего тона, а отчасти — переносимость. Некоторые версии могут неверно истолковать наличие кавычек внутри других кавычек, так как, по сути, это спецсимволы. У меня, например, глаз сразу зацепился, я подумал, что строка прерывается. В C, скажем, так же. Кавычки и все спецсимволы внутри строк следует экранировать во избежании ошибок компиляции или двойного толкования.
0
cooch #
Честно говоря, думал что одинарные и двойные кавычки в питоне как раз для того, чтобы избежать их экранирования.
0
Liksys #
Это, скорее, дань прошлому. Многие питонеры в одинарные кавычки заключают один единственный символ (как C/C++), а в двойные — строку.
+5
nicosha #
Благодаря таким как вы… — я потихоньку изучаю питон.
0
Bytamine #
absx = dirname+os.sep+x

Вы же ниже используете os.path.join, зачем тут такой изворот? :)
0
Bytamine #
ну и
elif not os.path.exists(imdir):

лучше
elif not os.path.isdir(imdir):
0
ddsl #
А тут можно проследить мою миниэволюцию ) Сначала написал с конкатенацией, потом понял что можно os.path.join, но не везде исправил, неуглядел.
0
Kotyamba #
молодца
0
ddsl #
я тоже считаю, что отработал своюй 7ап :)
0
andoriyu #
А можно модернизировать скрипт: задавать 2 директории(откуда и куда) а также смотреть соотнощение сторон и сувать это на первый уровень (16:9/1280x720)?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.