Pull to refresh

Linux в кармане — на службе у фотографа

Reading time 6 min
Views 49K
Так получилось, что фотография, это мой основной профессиональный вид деятельности, а программирование — хобби, которое иногда позволяет размять мозг. Кроме непосредственно разминки для мозга, программирование помогает и в работе. Например, писал полезные штуки, такие как это или это, или это.

Недавно поставил себе задачу, как бы еще порадовать своих клиентов. Вспомнил многочисленные просьбы клиентов на свадебную съемку: «Как хорошо было бы, если бы на банкете вы смогли показать коротенькое слайдшоу из фотографий, которые отсняли за день». На эти просьбы приходилось отказывать, по нескольким причинам: лень таскать с собой ноутбук для сборки слайдшоу, нет времени на отбор пары десятков снимков из сотен, из raw опять же нужно конвертировать, и самое главное — на это все нужно время, которого нет.

Это рассказ, о том, как мне удалось сделать для себя инструмент, который с минимальным моим участием и минимальным дополнительным весом в рюкзаке, помогает сделать красивые слайдшоу. И конечно же рассказ о python, ffmpeg и linux на android.

Неожиданный выбор железа


Первая проблема — это лишний вес. Мне нужен был полноценный linux на достаточно приличном железе. Изначально мой выбор пал на Orange PI PC, о котором я услышал на гигтаймсе. Железка была заказана и доставлена. Мне казалось, это то что нужно — 4 ядра по 1.5 ггц, 1 гб оперативной памяти и полноценные USB. Но на деле, лишний раз убедился, что без нормальной поддержки, все «клоны raspberry», ничего не стоят. Очень глючные образы OS, постоянно отваливающиеся ядра под нагрузкой, проблема с работой библиотек, чтоб, например подключить lcd дисплей.
И самое главная проблема, это неожиданный killed, при свободных 800 мб оперативки, на участке кода типа:
from PIL import Image
img=Image.new('RGB',(6000,4000)) #на деле мне нужно было не создавать, а открывать фотографии
img.rotate()

Причем тоже самое прекрасно работало на нетбуке с 1гб оперативке без свопа, а так же на Raspberry PI первом. И уж тем более, и речи не могло быть, чтоб делать тоже самое, но на 4-х ядрах одновременно.

Решение пришло неожиданно, когда я взял в руки смартфон, чтоб прочитать пришедшее сообщение:
А в кармане то постоянно лежит железка с 2.2 ггц 4-х ядерным процессором, 2гб оперативном памяти + USB-otg имеется (Nexus 5). Осталось найти способ полноценного запуска Linux окружения. После отбрасывания различных вариантов с перепрошивкой (хотелось пользоваться им полноценно и как смартфоном), выход был найден — Linux Deploy. Если кратко, Linux Deploy запускает полноценное linux окружение в chroot'e (подробнее о программе можно почитать в блоге у нашего соотечественника — разработчика), и самое главное для меня — монтировать произвольный каталог из fs android в свое окружение. Без этого, не была бы возможна работа с картридером SD карт памяти, воткнутым в OTG разъем.

Отбор фотографий


Слайдшоу из сотен фотографий, заняло бы пару часов времени. Нужен был способ легко и быстро отобрать 20-40 фотографий. Пролистывать даже 100 фотографий со смартфона — то еще удовольствие, а количество может доходить к вечеру до тысячи (дубли с серийной съемки, брак, пристрелочные фото, репортаж и пр.)
Взглянув на фотоаппарат, вспомнил про кнопку, которой никогда не пользовался — спасительницей оказалась кнопка «rate», которая присваивает рейтинг фотографии:



Колесом прокрутки справа достаточно быстро пролистываются снимки, и на нужном нажимается кнопка «rate». Так как ты уже знаешь, что удачного за сегодня отснял, на все уходит не больше пары минут. Остается заставить программу найти и выбрать те снимки, которым присвоен хоть какой нибудь рейтинг.

Так как рейтинг попадает в exif, понадобится замечательный пакет exiftool (sudo apt-get install libimage-exiftool-perl) и wrapper к нему для python. А дальше все просто:

import os
import exiftool
all_files=[]

"""проходимся по всем файлам на SD карте """
for directory, dirnames, filenames in os.walk(PATH_TO_SD_ROOT):    
	for name in filenames:
		f=os.path.join(directory, name)
			if f.lower().endswith('.cr2') or f.lower().endswith('.jpg'): #и добавляем в список jpg и raw файлы
				all_files.append(f)
	tool=exiftool.ExifTool()  
	tool.start() #запускаем exiftool

        # просим пройтись по нашему списку и выдать рейтинги
	result=tool.get_tags_batch(['XMP:Rating'],all_files)
	rated_files=[]
	for x in result:
		if x['XMP:Rating']>0:
			rated_files.append(x['SourceFile'])
# на выходе получаем список нужных нам файлов
rated_files.sort()


Следующий этап достаточно тривиален — копирование нужных фотографий во временный каталог и резайс в несколько потоков для дальнейшей работы. Единственное, на чем бы хотел заострить внимание, это raw, в который я снимаю. Конвертацией занимается утилита dcraw (хотя это не полноценная конвертация, а лишь выдергивание вшитой jpg в raw файл, но в данном случае, это более чем достаточно.

import subprocess

for n,x in enumerate(self.rated_files):
	dcraw_opts = ["dcraw", "-e", "-c", x]    # -e - вытащить вшитый jpg, -с выдать нам его в stdout
	dcraw_proc = subprocess.Popen(dcraw_opts, stdout=subprocess.PIPE)		
	image = StringIO.StringIO(dcraw_proc.communicate()[0])  # берем фотографию со stdout
	image.seek(0)
	open('input/%02d.jpg'%(n),'wb').write(image.read()) # и записываем в нужное место.


Сделай мне красиво!



На предыдущем этапе можно было бы остановиться, взяв фотографии и пустив их как слайдшоу на ноутбуке диджея, подключенного к проектору, но хочется, чтоб все это выглядело красиво.
На помощь приходит такая замечательная вещь, как ffmpeg (avconv). Я не любитель каких либо ярких спецэффектов, мне достаточно легкой динамики, в виде zoom'a фотографии и «crossfade» перехода между слайдами. Скажу сразу, несмотря на огромные возможности ffmpeg, это сделать у меня не получилось. Например, фильтр zoompan, выдавал ужасное качество и дрожащую картинку. После недели, проведенные за чтением мануалов и форумов, решено было сделать это «в лоб»:

def processImage(numb):
	img=Image.open('input_temp/%02d.jpg'%numb) # открываем текущее изображение
        # для эффекта crossfade открывает следущее изображение, или если оно последнее, создаем пустое
	try:next_img=Image.open('input_temp/%02d.jpg'%(numb+1)).resize((1280,720),Image.ANTIALIAS)
	except:next_img=Image.new('RGB',(1280,720),'black')

        # чтобы не захламлять память сотнями отдельных кадров, попросим ffmpeg принимать на stdin фотографии
        # и получим на выходе готовый отрезок видео
	p = subprocess.Popen(['avconv', '-y', '-f', 'image2pipe', '-vcodec', 'mjpeg', '-r', '25', '-i', '-', '-vcodec', 'mjpeg','-q:v', '3' , '-r', '25', 'output/%02d.mjpg'%(numb)], stdin=subprocess.PIPE)


	# 100 кадров при 25 к/c - 4 секунды видео на слайд
	for x in xrange(100):
                # при каждой итерации делаем кроп исходной фотографии в соответствии с пропорцией 16:9
		n=img.crop((int(float(x)*16.0/9.0),x,int(1920.0-float(x)*16.0/9.0),1080-x))
                # и делаем резайс к конечному размеру
		n=n.resize((1280,720),Image.ANTIALIAS)
                # на третей секунде, начинаем "подмешивать" следующую фотграфию
		if x>75:
			n=Image.blend(n,next_img,float(x-75)/25)

                # и скармливаем ffmpeg'у
		n.save(p.stdin,'JPEG')
	p.stdin.close()
	p.wait()


Ах да, я что то там говорил про ядра процессора. Хотелось бы распаралеллить этот процесс, чтоб были заняты все ядра. В python это делается очень очень просто:
from multiprocessing import Pool
s=len(glob.glob('input_temp/*.jpg')) #берем количество фотографий 
pool = Pool()
pool.map(processImage, xrange(s)) # и отдаем их воркерам, количество которых будет равно количеству ядер процессора
pool.close()
pool.join()


В итоге мы имеем множество отрезков mjpeg видео, которые нужно соединить воедино, вставив музыку.
Погуглив, не нашел лучшего способа, как сначала напрямую соединить видео, используя cat:
cat 00.mjpg 01.mjpg ..... > out.mjpg

Осталось только переконвертировать его в нужный формат, добавив музыку:
avconv -threads 4 -framerate 25 -i out.mjpg  -i  audio.mp3  -shortest -y  -r 25  -preset veryfast out.mp4


Чтобы не возиться каждый раз в консоли, а нужно было выбирать музыкальный трек, вписывать название для слайдшоу (для первого кадра) и пр, поднял простой web сервер, который стартует при запуске Linux Deploy. Я использовал простенький фреймворк bottle. Выглядит это вот так:



Итого


2-3 минуты отбор фотографий, запуск Linux Deploy, localhost в браузере, пару секунд на то, чтоб вписать title и нажать на СТАРТ. Далее 10-15 минут работы смартфона, и видео готово:



Таким же способом можно делать не только слайдшоу из фотографий, но и склеивать видео: пометить нужные отрывки в камере кнопкой rate и склеить их потом ffmpeg'ом.

И небольшой анонс

Если эта тема окажется интересной, я сделаю еще несколько публикаций. Например, на очереди статья, как сделать вот такую милую и функциональную фотобудку:
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+66
Comments 75
Comments Comments 75

Articles