20090720

Как ускорить или замедлить видеоролик

Иногда нужно замедлить (растянуть) видеоролик, чтобы он игрался как будто в режиме замедленного воспроизведения, а иногда нужно наоборот, показать «на перемотке» слишком длинный ролик, выбросить часть кадров и ускорить воспроизведение. О том как это сделать — сия заметка.

Изменить частоту кадров в видеопотоке позволяет программа yuvfps из пакета mjpegtools. Как и большинство утилит пакета она принимает и отдаёт видеопоток в формате YUV4MPEG. И ffmpeg, и mencoder тоже умеют работать с YUV4MPEG (и умеют читать и писать всякие другие форматы). Я приведу пример использования ffmpeg.

Итак, чтобы ускорить видео, нужно взять исходный файл и посмотреть, какая в нём частота кадров
$ ffmpeg -i normal.ogg
FFmpeg version SVN-r13582, Copyright (c) 2000-2008 Fabrice Bellard, et al.
...
Input #0, ogg, from 'normal.ogg':
Duration: 00:00:10.49, start: 0.000000, bitrate: 150 kb/s
Stream #0.0: Video: theora, yuv420p, 320x240 [PAR 1:1 DAR 4:3], 30.00 tb(r)
Must supply at least one output file

В данном случае исходный файл — 30 кадров в секунду. Затем нужно решить, во сколько раз уменьшить число кадров (исходя из желаемой длительности ролика). Потом берём исходный файл (normal.ogg в примере) и преобразовываем его в YUV4MPEG-поток (первый вызов ffmpeg), после нужно дважды вызвать yuvfps, первый раз, чтобы изменить число кадров в потоке (yuvfps -s 5:2 -r 1:1 сокращает исходные 2.5 кадра до одного), второй раз, чтобы перезаписать заголовок потока, указав скорость воспроизведения (yuvfps -r 30:1 -c устанавливает скорость 30 кадров в секунду). В конце опять вызываем ffmpeg и кодируем в нужный формат (я сохранил в формате Ogg/Theora, чтобы можно было вставлять в веб-странички тэгом <video>). Всё вместе:
$ ffmpeg -i normal.ogg -sameq -f yuv4mpegpipe - | \
yuvfps -s 5:2 -r 1:1 | yuvfps -r 30:1 -c | \
ffmpeg -f yuv4mpegpipe -i - -sameq -y fast.ogg


Аналогично можно увеличить число кадров. Дополнительные кадры интерполируются:
$ ffmpeg -i normal.ogg -sameq -f yuv4mpegpipe - | \
yuvfps -s 1:3 -r 1:1 | yuvfps -r 30:1 -c | \
ffmpeg -f yuv4mpegpipe -i - -sameq -y slow.ogg

В этот раз видео замедляется в 3 раза: на каждую «треть» исходного кадра (-s 1:3) создаётся целый кадр (-r 1:1). Вообще, как легко заметить, в качестве частоты кадров для yuvfps можно указывать любые дроби в виде числитель:знаменатель.

Немного более гладкой интерполяции при увеличении числа кадров вроде бы можно добиться, если использовать yuvmotionfps, но чудес он не делает.

Теперь можно сравнить все три видео. Для просмотра нужен современный браузер с поддержкой тэга <video/>.

Исходное видео:



Ускоренное в 2.5 раза:



И замедленное в 3 раза:



P.S. В качестве иллюстрации использовал ролик Breitenlee-VESTAS-V-52.

20090714

Вы не поверите — Ubuntu Cola!

Эта заметка не про линукс. Эта заметка про газированный напиток Ubuntu Cola. Вот он:



Купил вчера в автомате в итальянском университете за 1,80 €. На этикетке спереди значок Fairtrade. Сзади надпись:
Ubuntu. “Я есть, потому что есть мы”. Благодаря программе Fairtrade производители тростникового сахара в Малави и Замбии могут заключать более выгодные контракты и вкладывать средства в социальные, экономические и природоохранные проекты. К тому же, мы возвращаем 15% нашей прибыли в эти страны через программу Ubuntu Africa. Посетите: www.ubuntu-trading.com
Вот такая кола. Вот такая убунту. Кстати, на вкус оказалась довольно хороша.

20090712

Необыкновенно лёгкий парсинг в Python

Нашёл просто волшебную библиотечку для парсинга в Python (хм, правильно говорить синтаксического анализа), pyparsing. Ниже на простом примере я покажу, как её можно использовать для разбора пользовательских форматов данных.

Нашёл так: читая Real World Haskell, узнал про комбинаторную библиотеку для синтаксического анализа Parsec. Примеры в книжке впечатлили. В отличие от традиционного подхода, при этом нет разделения на лексический анализ (выделение «слов»-лексем) и синтаксический анализ (преобразование потока «слов» в упорядоченную структуру данных) — в комбинаторном парсинге эти два этапа объединяются. Берутся небольшие функции, распознающие элементы текста, и затем они комбинируются в соответветствии с синтаксисом текста. Таким образом, сама комбинация функций непосредственно отражает грамматику, и она же, естественно, является и программой для разбора текста. Как у всякой удачной идеи, у Parsec есть множество подражаний. Для Python комбинаторных парсеров нашлось целых два уже три уже четыре — Pysec, Pyparsing, LEPL (для Python 2.6/3.0) и funcparselib. Я буду говорить о pyparsing.

В следующей заметке — Ещё одна библиотека для комбинаторного парсинга — смотрите аналогичный пример для библиотечки funcparserlib.


Итак, перейдём к делу. Предположим нужно читать файлы состоящие из записей следующего вида:
Inspection
# 2 SHOULD Ref. Sys 1
X 28.7493
Y 78.9960
Z -1.0014

Всё необходимое импортируем из модуля pyparsing. При работе поглядываем в документацию к модулю. Для простоты примера импортируем всё:
from pyparsing import *

Теперь начинаем описывать грамматику. Например, определим числа как слова, состоящие из цифр, знака точки и дефиса (минуса)
number = Word(nums + ".-")

а значения переменных определим как пару заглавной латинской буквы и числа:
var = Regex("[A-Z]") + number

Обратим внимание, что плюс между двумя простыми парсерами (регулярное выражение и слово) создаёт новый парсер, который распознаёт уже последовательность выражений. По-умолчанию pyparsing игнорирует все лишние пробелы и переводы строк между элементами разбираемого текста (обычно именно это и нужно), поэтому указывать в грамматике наличие пробелов между элементами необязательно.

Уже на этом этапе мы можем попробовать наш парсер переменных. Запускаем интерпретатор и выполняем:
>>> var.parseString("X   42.0")
(['X', '42.0'], {})

— на выходе получили структуру данных в соответствии с нашей грамматикой (имя переменной и число за ним).

Допишем всё остальное. Для простоты будем считать комментарием всё после знака «#» до конца строки (комбинатор restOfLine):
comment = "#" + restOfLine

Теперь мы можем описать грамматику всей записи в целом.
record = Suppress("Inspection" + comment) + OneOrMore(var)

Запись опознаём по слову «Inspection» в начале (здесь строковой литерал Python автоматически конвертируется в Literal-парсер, проверяющий буквальное соответствие слову). Далее, обнаружив начало записи, состоящие из слова «Inspection» и следующий за ней комментарий, мы можем их просто пропустить (комбинатор Suppress), а вот то, что следует дальше — нам интересно. Мы ожидаем, что дальше могут идти значения для одной или нескольких переменных (применяем комбинатор OneOrMore).

Последний штрих. Нужно указать, что в файле таких записей может быть несколько. Для удобства работы с полученной структурой переменные каждой из записей группируем вместе (комбинатор Group):
datafile = OneOrMore(Group(record))

Всё! Синтаксический анализатор для нашего формата данных готов. Использовать можно, например, так:
import sys
print datafile.parseString(sys.stdin.read())


Проверяем:
$ python example.py << END
> Inspection
> # 2 SHOULD Ref. Sys 1
> X 28.7493
> Y 78.9960
> Z -1.0014
>
> Inspection
> # 3 SHOULD Ref. Sys 1
> X 54.0394
> Y 64.3977
> Z -0.9950
>
> END
[['X', '28.7493', 'Y', '78.9960', 'Z', '-1.0014'],
['X', '54.0394', 'Y', '64.3977', 'Z', '-0.9950']]

Получили вполне пригодную к использованию в программе структуру данных. Вся грамматика — на пять строк. В общем-то, поняв идею и поглядывая в справку, несложно описать и более сложную грамматику.

Например, чтобы разбирать также и строчку с «#» в моём примере, программку можно изменить так:
from pyparsing import *
number = Word(nums + ".-")
var = Regex("[A-Z]") + number
desc = Suppress("#") + Word(nums) + Word(alphas) \
+ Suppress("Ref. Sys") + Word(nums)
record = Suppress("Inspection") + desc + Group(OneOrMore(Group(var)))
datafile = OneOrMore(Group(record))

На выходе этот парсер даст:
[['2', 'SHOULD', '1', [['X', '28.7493'], ['Y', '78.9960'], ['Z', '-1.0014']]],
['3', 'SHOULD', '1', [['X', '54.0394'], ['Y', '64.3977'], ['Z', '-0.9950']]]]


P.S. Нормального тьюториала по pyparsing в сети я не нашёл, но автор библиотеки написал и продаёт на O’Reilly учебное электропособие за 10 долларов. Справочная же документация и разные примеры в интернете — вполне толковы.

См. также заметку про funcparserlib.

20090701

Сортировка фото по дате EXIF

На днях и я, и virens почти синхронно написали две заметки об EXIF: как исправить дату EXIF в фото и как добавлять пользовательские теги в raw-файлы. Раз уж тема начата, то поделюсь ещё одним приёмом.

Фотографии я храню, группируя каталоги по годам и по датам съёмки (указывая дату в формате ISO), то есть в архиве путь к альбому у меня примерно такой: photos/2009/20090628 - название альбома/. Очень удобно, потому что обычно один день — одна тема, и даже при алфавитной сортировке каталога альбомы упорядочены хронологически. И такая организация не зависит ни от операционной системы, ни от конкретной программы-каталогизатора.

Однако если на карточке фотографии разных дней — раскидывать их по альбомам вручную утомительно. Поэтому у меня есть ещё и скрипт-сортировщик для внесения фото в архив. Он смотрит на дату в EXIF, создаёт нужные каталоги и помещает в них фото:
#!/bin/sh

ARCHIVE=$HOME/photos

for f in "$@"; do
DT=$(exiftool -s -DateTimeOriginal "$f")
YEAR=$(echo $DT|awk '{print $3;}'|awk -F: '{print $1;}')
ISODAY=$(echo $DT|awk '{print $3;}'|sed 's/://g')
TARGET="$ARCHIVE/$YEAR/$ISODAY"
install -d "$TARGET" && \
install "$f" "$TARGET"
echo "$f -> $TARGET"
done


Запускаю из каталога с фотокарточки:
$ import-photos *

Так можно импортировать и JPEG-и, и RAW. И там, и там EXIF обычно есть.

Как ускорить Firefox на eeePC 901

На моём eeePC 901 Firefox часто подтормаживал, и это явно совпадало с работой диска. Объяснение нашлось здесь.

Дело в том, что в eeePC 901 два флэш-диска, маленький на 4 ГБ и большой 16 ГБ, и этот большой флэш-диск — медленный. Домашний же раздел /home у меня, естественно, на большом, и там же профиль Firefox-а. Браузер же по-умолчанию сохраняет в него текущую сессию каждые 10 секунд и туда же пытается писать кэш. Пишет он синхронно (то есть ждёт, пока не запишется), поэтому и замирает на секунду при каждом чихе.

Народная медицина в этом случае рекомендует пойти в about:config и там

1) создать ключ toolkit.storage.synchronous с целым значением 0 (запись на диск вести асинхронно, то есть не ждать, пока, например, состояние сессии действительно запишется)

2) создать или изменить ключ browser.cache.disk.parent_directory, его строковое значение установить в /dev/shm/firefox-username (сохранять кэш в памяти, а не писать на диск; кэш, конечно, будет утерян при перезагрузке, и памяти потребуется больше, но зато и работать будет быстрее)

Дополнительно я увеличил промежуток времени между записями состояния сессии (списка открытых вкладок):

3) в browser.sessionstore.interval поставил 60000 (60 секунд), вместо 10000 (10 секунд).

Я попробовал — так явно лучше (iceweasel 3.0.6). Возможно, с кэшем есть и более красивые решения (сделать внутри профиля символическую ссылку куда-нибудь на быстрый диск, или какими-то другими ключами отключить дисковый кэш, увеличив кэш в памяти...). Можете оставлять в комментариях ссылки и рецепты.

Советы, полагаю, применимы не только к eeePC, но и к другим нетбукам с SSD (флэш-дисками).

В предыдущей заметке можно прочитать, как мы ставили и настраивали Debian на eeePC.