[

воскресенье, 29 декабря 2019 г.

Ubuntu + CoreDNS + DNS-over-TLS


/etc/coredns.conf

.:53 { any # Google forward . tls://8.8.8.8 tls://8.8.4.4 { tls_servername dns.google health_check 5s } # Cloudflare #forward . tls://1.1.1.1 tls://1.0.0.1 { # tls_servername cloudflare-dns.com # health_check 5s #} # Just in case #forward . 8.8.8.8:53 8.8.4.4:53 1.1.1.1:53 errors log }

/etc/systemd/system/coredns.service

[Unit] Description=CoreDNS Service
DefaultDependencies=no
After=systemd-sysusers.service systemd-networkd.service
Before=network.target nss-lookup.target shutdown.target
Conflicts=shutdown.target
[Service] Type=simple User=root Group=root
Restart=always
RestartSec=0
WatchdogSec=3min
ExecStart=/sbin/coredns -conf /etc/coredns.conf [Install] WantedBy=multi-user.target

/etc/resolv.conf

nameserver 127.0.0.1

Commands

Setup

wget https://github.com/coredns/coredns/releases/download/v1.6.6/coredns_1.6.6_linux_amd64.tgz tar xzf coredns_1.6.6_linux_amd64.tgz sudo mv coredns /sbin/ # ... config files ...

Run

sudo systemctl daemon-reload sudo systemctl enable coredns sudo systemctl restart coredns sudo systemctl status coredns

пятница, 7 августа 2015 г.

Перевод guest instance в Ganeti на PvGrub загрузку

За основу взят пост http://blag.welterde.de/posts/2014/06/29-ganeti-pvgrub.html

Имеем ganeti cluster 2.11 (ubuntu 14.04), xen-pvm, debian 7 guest

Внутри гостя выполняем действия из хука (установка grub, 40_custom, ядра).

Ставим pvgrub, например, http://celttechie.github.io/installing-configuring-pv-grub.html (требует обработки напильником).

NB: не забываем сделать gnt-cluster copyfile для pv-grub файлов.
Далее правим параметры instance:
$ gnt-instance modify -H kernel_args='(hd0\,0)/boot/grub/menu.lst' instance_name
$ gnt-instance modify -H kernel_path='/usr/lib/xen/boot/pv-grub-x86_64.gz' instance_name
$ gnt-instance modify -H initrd_path='' instance_name
$ gnt-instance modify -H bootloader_path='' instance_name
$ gnt-instance modify -H bootloader_args='' instance_name
$ gnt-instance modify -H root_path='' instance_name

Ребут инстанса через gnt-instance reboot.

понедельник, 11 августа 2014 г.

Инициализация Ganeti кластера на Ubuntu 14.04 и Xen 4.4

Все по мануалу http://docs.ganeti.org/ganeti/2.11/html/install.html.

Кластер инициализировал так:

gnt-cluster init --enabled-hypervisors=xen-hvm,xen-pvm --nic-parameters link=xenbr0 --master-netdev xenbr0 cluster.host.name

пятница, 1 августа 2014 г.

GORM strangeness

Grails version 2.3.8


Вот такая конструкция не работает

runners = Runner.methodReturningDetachedCriteria(params).findAll {
    lastName =~ "%${lastName}%"
}

Такая тоже

def filter = {
    lastName =~ "%${lastName}%"
} as DetachedCriteria<Runner>
runners = Runner.methodReturningDetachedCriteria(params).findAll(filter)

А такая вполне себе работает

def filter = {
    lastName =~ "%${lastName}%"
} as DetachedCriteria<Runner>
runners = Runner.methodReturningDetachedCriteria(params).where(filter).findAll()

четверг, 15 мая 2014 г.

Hibernate Exceptions In Grails Domain Event


По неосторожности можно наткнуться на

ERROR hibernate.AssertionFailure  - an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session) Message: null id in Domain entry (don't flush the Session after an exception occurs)

пытаясь изменить свойства домена, например, в событии beforeInsert.

Простой пример:
def beforeInsert() {
    someField = fieldModificationService.modify(someField)
}

Если Hibernate сам управляет сохранением данных в базе, а по умолчанию это так, то тут может произойти flush, что в контексте события домена запрещено.

Чтобы этого не происходило можно обернуть сервис в @Transactional(readOnly = true):
@Transactional(readOnly = true)
class FieldModificationService {
    def grailsApplication
    ...
}

воскресенье, 27 апреля 2014 г.

Not In Groovy

5ти минутный ступор: в Groovy есть оператор in, но не not in... Python портит людей.

понедельник, 9 сентября 2013 г.

Как вызвать интерактивную консоль python из кода

bpdb

import bpdb; bpdb.set_trace()

На экране появится приглашение bpdb
Django version 1.5.2, using settings 'my_project.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
> /path/to/my_project/app/views.py(224)visit_update()
-> return HttpResponse(json.dumps("TODO:"), content_type="application/json")
Use "B" to enter bpython, Ctrl-d to exit it.
(BPdb)

NB: bpython не работает, но по идее это должно быть так:
import bpython; bpython.embed()

IPython

import IPython; IPython.embed()

Python code (last resort)

import code; code.InteractiveConsole(locals = locals()).interact()

воскресенье, 11 августа 2013 г.

Установка virtualenv под Windows

Устанавливаем Python

Скачиваем MSI файл от сюда http://python.org/download/

Устанавливаем setuptools (нужны для установки pip)

Скачиваем специальный скрипт с https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py и запускаем

C:\> C:/Python27/python.exe C:/path/to/ez_setup.py

Устанавливаем pip

Скачиваем дистрибьютив с https://pypi.python.org/pypi/pip, распаковываем и запускаем установку

C:\> C:/Python27/python.exe C:/path/to/pip-distrib/setup.py install

Устанавливаем virtualenv

C:\> C:/Python27/Scripts/pip.exe install virtualenv

Проверяем virtualenv

C:\>mkdir tmp

C:\>cd tmp

C:\tmp>C:\Python27\Scripts\virtualenv.exe test
New python executable in test\Scripts\python.exe
Installing Setuptools...........................................................
................................................................................
................................................................................
.......................done.
Installing Pip..................................................................
................................................................................
................................................................................
................................................................................
.....................done.

C:\tmp>cd test

C:\tmp\test>Scripts\activate.bat
(test) C:\tmp\test>

суббота, 10 августа 2013 г.

четверг, 21 февраля 2013 г.

Музыка по дням недели

Пн - Radiohead, Gorillaz
Вт - Radiohead, Gorillaz, Depeche Mode
Ср - Depeche Mode, Muse
Чт - Metallica, Rammstein, etc...
Пт - Группировка Ленинград.

воскресенье, 3 февраля 2013 г.

Конвертация физического linux сервера в виртуальную машину XCP (XenServer)

Описанные действия производились с физическим серверов на Debian Squeeze 32, а перенос осуществлялся на виртуальную машину под XCP 1.6 (соответствует XenServer 6.1).

К сожалению из этой версии установочного диска выпилили утилиту для автоматической конвертации физического сервера в виртуальны (p2v) для linux.

Общий план действий такой:
  1. Получим образ диска сервера
  2. Создадим HVM машину
  3. Перенесем образ диска физического сервера на диск виртуального
  4. Переделаем HVM машину в PVM
Ничего сложного:)

Получение образа диска физического сервера

Выключаем сервер и грузимся с live cd. Я использовал установочный диск Ubuntu Desktop.

Далее надо куда-то сохранить наш образ. Я воспользовался внешним хранилищем, подключаемым по USB. Не самый быстрый вариант, но ~20-30 Мб/с будет.

Итак, диск для клонирования /dev/sdb. Клонировать будем в /dev/sdc1. Команды:
# mount /dev/sdc1 /mnt/
# dd if=/dev/sdb of=/mnt/linux_system_disk bs=512K
Копироваться будет долго, никакой диагностики на экране не будет, чтобы ее увидеть нужно дать сигнал USR1 процессу dd (kill -USR1 <pid>).

Как скопируется сервер можно перезапустить в штатном режиме, если это, конечно, надо.

Запишем размер полученного образа (диск на создаваемой виртуалке должен быть не меньше этого размера):
# ls -l /mnt/
-r-------- 1 root root 159988580352 Янв 24 04:58 linux_system_disk

Создание HVM машины

Сделаем копию шаблона для установки Debian Squeeze 32 bit, но на HVM машину. Найдем шаблон для Debian Squeeze 32 bit:

# xe template-list | grep -B1 "Debian Squeeze 6.0 (32-bit)"
uuid ( RO)                : 8aac6fb4-bed6-99c9-6b17-5aebbdb5fb25
          name-label ( RW): Debian Squeeze 6.0 (32-bit)

Клонируем шаблон и правим параметры:
# xe vm-clone uuid=8aac6fb4-bed6-99c9-6b17-5aebbdb5fb25 new-name-label="Debian Squeeze 6.0 (32-bit)"
419070db-67c0-9f13-6259-ce2039142ddf
# xe template-param-set  PV-args="" uuid=419070db-67c0-9f13-6259-ce2039142ddf
# xe template-param-set  PV-bootloader="" uuid=419070db-67c0-9f13-6259-ce2039142ddf
# xe template-param-set  HVM-boot-policy="BIOS order" uuid=419070db-67c0-9f13-6259-ce2039142ddf
# xe template-param-set  HVM-boot-params:order="dc" uuid=419070db-67c0-9f13-6259-ce2039142ddf
# xe template-param-set other-config:disks='<provision><disk device="0" size="159988580352" sr="" bootable="true" type="system"/></provision>' uuid=419070db-67c0-9f13-6259-ce2039142ddf

Шаблон готов. Из него можно создать машину и загрузиться с помощью live cd.

Добавим Ubuntu Desktop 12.10 install cd в список VDI хост машины.

# mkdir -p /var/opt/xen/iso_import/ubuntu
# mv ubuntu-12.10-desktop-i386.iso /var/opt/xen/iso_import/ubuntu/
# xe sr-create name-label=ubuntu type=iso device-config:location=/var/opt/xen/iso_import/ubuntu device-config:legacy_mode=true content-type=iso

Проверим нашелся ли диск с Ubuntu:
# xe vdi-list 
uuid ( RO)                : f0dada79-eb6e-4eea-8198-a181e6390bca
          name-label ( RW): ubuntu-12.10-desktop-i386.iso
    name-description ( RW): 
             sr-uuid ( RO): 8270e933-c298-c925-599b-e87951fac965
        virtual-size ( RO): 789884928
            sharable ( RO): false
           read-only ( RO): true

Теперь можно создать виртуальную машину, добавить ей сетевой интерфейс (пусть на нем будет DHCP, да и с доступом к интернету), "вставить" Ubuntu CD в данную виртуальную машину и запуститься без установки.

# xe vm-install template="Debian Squeeze 6.0 (32-bit) HVM" new-name-label="Sample P2V"
b724c232-af4b-806d-cc35-efa273015248
# xe vm-cd-add vm=b724c232-af4b-806d-cc35-efa273015248 cd-name=ubuntu-12.10-desktop-i386.iso device=3
# xe vbd-list vm-uuid=b724c232-af4b-806d-cc35-efa273015248 type=CD params=uuid
uuid ( RO)    : 38798b08-2cf5-3816-3f40-3c73ef456b62
# xe vbd-param-set uuid=38798b08-2cf5-3816-3f40-3c73ef456b62 bootable=true
# xe network-list 
uuid ( RO)                : 5c2bb576-7836-70e9-f08f-2f0e172075ea
          name-label ( RW): Pool-wide network associated with eth0
    name-description ( RW): 
              bridge ( RO): xenbr0

# xe vif-create network-uuid=5c2bb576-7836-70e9-f08f-2f0e172075ea vm-uuid=b724c232-af4b-806d-cc35-efa273015248 device=4
17865728-5c69-b2a9-8bf7-75cd7add4550

# xe vm-start uuid=b724c232-af4b-806d-cc35-efa273015248

Машина запущена, теперь надо подключиться к ней по VNC и записать образ.

Перенос образа на виртуальную машину

Если это первая виртуальная машина, то ее VNC слушает на 5901 порту, но порт этот закрыт iptables. Чтобы подключиться я предпочитаю старый трюк с пробросом портов через ssh:

$ ssh -L 127.0.0.1:5901:0.0.0.0:5901 <xcp_host_name_or_ip>
$ vncviewer 127.0.0.1:5901

Должно открыться окно терминала. Но! оно может/будет закрываться при изменении разрешения в процессе загрузки VM, это нормально, запустите viewer заново.

Виртуальная машина загрузилась. Теперь вставляем USB хранилище в хост машину и монтируем, например, в /mnt/.

Считая, что Ubuntu в VM имеет доступ к интернету, ставим sshfs и монтируем /mnt/ из хост машины в /mnt/ виртуалки:

$ sudo apt-get install sshfs
$ sshfs <xcp_host_name_or_ip>:/mnt/ /mnt/

Перенесем образ диска физического сервера (/mnt/linux_system_disk в виртуальной машине) на диск виртуальной машины (/dev/xvda). В этом нам поможет все тотже dd:
# dd if=/mnt/linux_system_disk of=/dev/xvda bs=512K

Ждем...

Когда образ будет перенесем, можно перезагрузить (в HVM режиме) виртуальную машину уже с диска физического сервера:
# init 6

Преобразование HVM машины в PVM

После того как виртуальная машина стартовала, ее надо переделать в PV. Для этого сначала поставим ядро с поддержкой xen domU, а затем поправим параметры VM.

Установка ядра с поддержкой domU (внутри VM):
apt-get install linux-image-2.6.32-5-xen-686

NB! Если вы не используете UUID в fstab, то в данный момент необходимо поменять sd*/hd* на vxd*.

Выключаем VM.

Правим параметры загрузки VM:
# xe vm-param-set uuid=b724c232-af4b-806d-cc35-efa273015248 HVM-boot-policy=""
# xe vm-param-set uuid=b724c232-af4b-806d-cc35-efa273015248 HVM-boot-params:=""
# xe vm-param-set uuid=b724c232-af4b-806d-cc35-efa273015248 PV-args="-- quiet console=hvc0"
# xe vm-param-set uuid=b724c232-af4b-806d-cc35-efa273015248 PV-bootloader="pygrub"

Все. Можно снова загружать VM (уже PVM).

пятница, 25 января 2013 г.

Установка и настройка phpdaemon на девственно чистом debian squeeze

Появилась необходимость поднять WebSocket сервер, а так как проет пишется на php, то тут особых вариантов и не было - phpdaemon. Немного погуглив нашел сравнительную статью Нагрузочное тестирование: Node.JS vs phpDaemon. Результаты порадовали и было решено попробовать это чудо (скажу только, что пока нареканий нет, la упал на порядок + разгрузили fcgi воркеров).

Установка Debian

Установочный образ debian-6.0.6-amd64-netinst.iso
При установке выбрал: Сервер SSH, Стандартные системные утилиты

Требования phpdaemon

Ставить будем из репо, по-этому необходим git:
# apt-get install git

Новый phpdaemon (от 23 января 2013) требует php 5.4. Поставить можно так (взято с serverfault http://serverfault.com/questions/404815/how-can-i-upgrade-php-to-a-higher-version-for-debian-squeeze):
# echo "deb http://ftp.ru.debian.org/debian/ wheezy main non-free contrib" >> /etc/apt/sources.list

# cat >> /etc/apt/preferences <<EOF
Package: *
Pin: release n=squeeze
Pin-Priority: 650

Package: *
Pin: release n=wheezy
Pin-Priority: -10
EOF

apt-get update
apt-get install -t wheezy php5-cli php5-dev php-pear

Из требований самого phpdaemon:
# apt-get install -t wheezy libevent-dev
# pecl install https://bitbucket.org/osmanov/pecl-event/downloads/event-1.0.0.tgz<
# echo "extension=event.so" > /etc/php5/conf.d/event.ini

# pecl install channel://pecl.php.net/proctitle-0.1.2
# echo "extension=proctitle.so" > /etc/php5/conf.d/proctitle.ini

# cd /usr/src/
# git clone https://github.com/zenovich/runkit.git
# cd runkit/
# pecl install package.xml
# echo "extension=runkit.so" > /etc/php5/conf.d/runkit.ini

# pecl install eio
# echo "extension=eio.so" > /etc/php5/conf.d/eio.ini

Стоит проверить наличие следующих модулей:
# php -m | grep pcntl
# php -m | grep shmop
# php -m | grep sockets

Установка phpdaemon

Репо на github: https://github.com/kakserpom/phpdaemon

Ставим:
# cd /opt/
# git clone https://github.com/kakserpom/phpdaemon.git

Настройка phpdaemon

Конфиги лежат в /opt/phpdaemon/conf/ и /opt/phpdaemon/conf/conf.d/, я предпочитаю их в /etc/phpdaemon/.

Копируем конфиги:
# mkdir /etc/phpdaemon
# cp /opt/phpdaemon/conf/phpd.conf.example /etc/phpdaemon/phpd.conf
# cp /opt/phpdaemon/conf/AppResolver.php /etc/phpdaemon/
# cp -rp /opt/phpdaemon/conf/conf.d /etc/phpdaemon/

AppResolver.php служит для роутинга приложений (но! не для WebSocket. для WS ws://server/RouteForAppName).

Правим основной конфиг /etc/phpdaemon/phpd.conf:
user www-data;
group www-data;
  
max-workers 1;
min-workers 1;
start-workers 1;
max-idle    0;
  
# logging-related
logevents       1;
logqueue        1;
logreads        1;
logsignals      1;
verbose         0;
verbosetty      0;
  
# apps search
appfilepath '{app-*,applications,/var/www/apps-phpdaemon}/%s.php';

# apps routing
path '/etc/phpdaemon/AppResolver.php';

# Pools
Pool:WebSocketServer {
    enable 1;
    listen '0.0.0.0';
    port 8047; 
}
  
Pool:HTTPServer {
    enable 1;
    listen '0.0.0.0';
    port 8080;
    expose 1;
}
  
# Apps
# для фоллбэка на http если клиент не поддерживает websocket 
WebSocketOverCOMET {
    enable 1;
}

# Для теста доступности по HTTP
Example {
    enable 1;
}

# Для теста WS с фолбэком на comet и longpolling
ExampleWebSocket {
    enable 1;
}

# прочие конфиги/приложения
#include conf.d/*.conf

Есть один нюанс: пул WebSocketServer должен быть в конфиге до пула HTTPServer, так как на данный момент при инициализации HTTPServer получает инстанс WebSocketServer. Если последнего нет, то он создается, но не пул а отдельный сервер. Если указано несколько воркеров, то только первый сможет сделать бинд для сокета WS.

appfilepath - параметр для поиска приложений. Наши приложения будут лежать в /var/www/apps-phpdaemon/. Параметр передается в glob() так:
if (class_exists('Daemon', false) && isset(Daemon::$config->appfilepath->value)) {
    $files = glob($g = sprintf(Daemon::$config->appfilepath->value, str_replace('_', DIRECTORY_SEPARATOR, $classname)), GLOB_BRACE);
    if (isset($files[0])) {
        require $files[0];
        return;
    }
}

Создадим папку для наших приложений:
# mkdir /var/www/apps-phpdaemon/

Поправим роутер приложений (/etc/phpdaemon/AppResolver.php), чтобы дать доступ к приложению ExampleWebSocket:
- if (preg_match('~^/(WebSocketOverCOMET|Example)/~', $req->attrs->server['DOCUMENT_URI'], $m)) {
+ if (preg_match('~^/(WebSocketOverCOMET|Example|ExampleWebSocket)/~', $req->attrs->server['DOCUMENT_URI'], $m)) {

Тестовый запуск

Запуск:
# php /opt/phpdaemon/bin/phpd start

В файле phpd в качестве интерпретатора указан /opt/bin/php, поэтому нужен запуск в виде php /opt/phpdaemon/bin/phpd.

Проверяем, что все "взлетело":
# ps aux | grep phpd
root     24225  0.5  1.3  95520  6968 ?        SNs  14:15   0:00 phpd: master process                                                                                                           
www-data 24226  0.3  1.4  96040  7272 ?        SN   14:15   0:00 phpd: IPC process                                                                                                              
www-data 24227  0.5  1.6  96592  8196 ?        SNl  14:15   0:00 phpd: worker process                                                                                                           
root     24230  0.0  0.1   7572   868 pts/0    S+   14:16   0:00 grep phpd

# netstat -ntpa | grep phpd
tcp        0      0 0.0.0.0:8047            0.0.0.0:*               LISTEN      24225/phpd: master 
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      24225/phpd: master 


Если теперь вбить в браузере http://server-address:8080/ExampleWebSocket/ то должны увидеть следующее.

Кнопка Create WebSocket инициирует соединение, Ping - отправляет запро, Close WebSocket - закрывает соединение.

Посмотреть пакеты WS (в хроме) можно щелкнув на соответствующий запрос на установку WS соединения.



Остановить phpdaemon можно так:
# php /opt/phpdaemon/bin/phpd stop

init скрипт (из репо не подходит для squeeze), ротация логов и прочее - на ваше усмотрение.

Постскриптум

Где-то по ходу пропустил зависимость и у меня поставился apache, хотя он и не нужен в данном случае и может только мешать, если HTTPServer phpdaemon будет слушать на 80м порту.

понедельник, 26 ноября 2012 г.

Формат hostname в URI

Периодически возникает вопрос как должен выглядеть REGEXP для domainname. Вот спецификация (пока  только BNF):


hostname      = *( domainlabel "." ) toplabel [ "." ]
domainlabel   = alphanum | alphanum *( alphanum | "-" ) alphanum
toplabel      = alpha | alpha *( alphanum | "-" ) alphanum

Взято из RFC: http://www.ietf.org/rfc/rfc2396.txt

PS: to be continued...

вторник, 23 октября 2012 г.

Замена табов на пробелы в VIM

Способов много, но больше всего понравился из комментария вот тут: http://stackoverflow.com/questions/426963/replace-tab-with-spaces-in-vim

:retab

Табы будут заменены на пробелы в соответствии с текущими настройками.

PS: после этого, возможно, захочется поправить отступы. это можно сделать так: gg=G. В командном режиме. Дословно это значит "перейти в начало файла, сделать реформатирование текста до конца". = сработает и в режиме выделения.

воскресенье, 7 октября 2012 г.

Стриминг IP камеры с сайта

Как делал

Есть IP камера производства AXIS. Она отдает RTSP и MJPEG. Надо live стримить ее поток с сайта через какой-нибудь flash плеер.

Пошел по привычному пути - ffmpeg. У них есть сервер для стриминга - ffserver.

Скажу сразу, что с RTSP намучился вусмерть! С MJPEG все оказалось проще.

Покопавшись с форматами и контейнерами уяснил, что далеко не все контейнеры можно стримить. Можно, например, FLV или ASF. Так как наша цель flash, взял FLV.

Далее надо было конвертировать. Как оказалось параметры надо указывать в настройках ffserver, а ему поток можно скармливать через ffmpeg, например.

Конфиг ffserver:
Port 8888
BindAddress 0.0.0.0

# другие опции...

# Конфигурация feed - входящий видеопоток
<Feed cam1.ffm>
    File /tmp/cam1.ffm
    FileMaxSize 10M
    # скармливать видео можно только с данной машины
    ACL allow 127.0.0.1
</Feed>

# Конфигурация выходного потока
<Stream cam1.flv>
    Format flv
    Feed cam1.ffm
    VideoCodec flv
    # без +global_header live stream не получится
    AVOptionVideo flags +global_header
    # для простоты в выходном потоке должно быть столько же кадров в секунду
    # сколько в входном, иначе картинка _может_ рассыпаться
    VideoFrameRate 25
    VideoBitRate 1000k
    VideoSize 512x384
    # нас интересует только видео
    NoAudio
</Stream>

Запуск ffserver (в конфиге по умолчанию стоит опция NoDaemon а лог выдается в stdout):
/path/to/ffserver -f /path/to/ffserver.conf

Все сервер работает... но ничего не стримит, так как на вход ничего не подается. Команда для забора нашего MJPEG с камеры и перенаправление его в feed ffserver:
/path/to/ffmpeg -f mjpeg -i http://login:pass@cap_ip/mjpg/video.mjpg http://localhost:8888/cam1.ffm

Перед указанием выходного файла никакие параметры указывать не надо - они берутся из конфига фида в ffserver.conf и будут проигнорированы.

Для проверки сделал тестовую страницу с плеером:
<html>
<head>
<title>Test IP Cam</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="/flowplayer/flowplayer-3.2.11.min.js"></script>
<script>
$(document).ready(function(){
    flowplayer("player", "/flowplayer/flowplayer-3.2.15.swf");
});
</script>
</head>

<body>
<a id="player" style="display:block;width:512px;height:384px;" href="http://ffserver_ip:8888/cam1.flv"></a>
</body>
</html>

Что дальше

А дальше это бы гнать в h264 (контейнер F4V). Беда в том, что конфиг файл ffserver не понимает presets и все параметры надо указывать через AVOptionVideo/AVOptionAudio.

воскресенье, 15 июля 2012 г.

Конвертирование видео для iPad new с ffmpeg

ffmpeg компилировал сам (если не забуду - выложу команды из хистори).


/opt/ffmpeg/bin/ffmpeg -y -i <in_file> -vcodec libx264 -acodec libfaac -f mp4 <out_file>.mp4

PS: поддерживаемые форматы нашел тут: http://www.isoftcoupon.com/tutorial/the-new-ipad-3-video-formats.html

вторник, 13 марта 2012 г.

Пометка элемента как просмотренного с jquery

Скрипт-игра. не скажу где используется и на что похоже;) Но это чудо эффективно может определить виден ли элемент клиенту (в текущем viewport'е) и как-то его отметить.

Собственно, листинг тестовой версии:

<html>
<head>
<title>TEST :: is element visible in viewport</title>

<script language="javascript" type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>

<script>
/*
 * Получение текущего вьюпорта:  координаты верхнего левого угла и ширина с высотой
 */
function current_viewport()
{
 var w = $(window);
 
 return {
  "left": w.scrollLeft(),
  "top": w.scrollTop(),
  "width": w.width(),
  "height": w.height()
 };
}


/*
 * Проверка находится ли объект во вьюпорте
 *
 * paddings = {
 *     "left": ...,
 *     "top": ..., 
 *     "right": ...,
 *     "bottom": ...
 * }
 * эффективно увеличивают размеры объекта. по умолчанию 0
 */
function in_viewport(selector, paddings)
{
 var el = $(selector);
 var off = el.offset();
 var dim = {
  "width": el.width(),
  "height": el.height()
 };
 
 if(!paddings)
 {
  paddings = {
   "left": 0,
   "top": 0,
      "right": 0,
      "bottom": 0
  };
 }
 
 var viewport = current_viewport();
 
 return (
  (off.top - paddings.top) >= viewport.top &&
  (off.left - paddings.left) >= viewport.left &&
  (off.top + dim.height + paddings.bottom) <= (viewport.top + viewport.height) &&
        (off.left + dim.width + paddings.right) <= (viewport.left + viewport.width)
 );
}

/*
 * Помечает объект-ответ, как прочтенный
 */
function mark_as_read(obj)
{
    $(obj).css({
        "border": "1px solid #eeeeee"
    });
    $(obj).data('is_read', true);
}


$(document).ready(function() {
 
 // паддинги для проверки попадает ли объект во вьюпорт
 var paddings = {
        "left": 0,
        "top": 0,
        "right": 0,
        "bottom": 0
    };
 
 for(var i = 0; i < 40; i++)
 {
  $('<div class="ticket_reply"></div>').css({
   "height": "100px",
   "margin-bottom": "20px",
   "border": "1px solid blue"
  }).appendTo('body');
 }
 
 var replies = $('.ticket_reply');
 
 replies.each(function(i) {
  // отметим видимые при загрузке
  if( in_viewport(this, paddings) )
     mark_as_read(this);
 });
 
 // зарегистрируем проверку прочтения сообщений по скроллингу
 $(window).scroll(function() {
  replies.each(function(i) {
   if( !$(this).data('is_read') && in_viewport(this, paddings) )
       mark_as_read(this);
  });
 });
 
});
</script>

</head>
<body>

</body>
</html>

За основу взят вот этот пост на stackoverflow: http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport

суббота, 24 декабря 2011 г.

Django 1.4 alpha release

На днях писал о кастомных фильтрах в админке: http://readability-counts.blogspot.com/2011/12/django-admin-listfilter.html. Все оказалось не совсем так - я смотрел trunk репозитория. И вот вышла Django 1.4 alpha с новой фичей, описанной в моем предыдущем посте. Там еще много всего интересного - изучайте!

четверг, 22 декабря 2011 г.

Django Admin и кастомный фильтр для list_filter

Это только набросок - нужно все опробовать на живом коде.

"Вдруг" родилась задача выводить кастомный список значений для фильтра в админке. Поясню, имеем следующую модель:
class NewsPost(models.Model):
    """Новость"""

    authors = models.ManyToManyField(User, blank = True, null = True)

В админке она представлена так:
class NewsPostAdmin(admin.ModelAdmin):
    """Представление Новости в админке"""

    # фильтрация новостей по автору
    list_filter = ('authors',)

    def formfield_for_manytomany(self, db_field, request, **kwargs):
        """Специальная фильтрация для списка допустимых авторов

        Данная фильтрация распространяется только на поле authors в
        форме редактирования новости в админке
        """

        if db_field.name == 'authors':
            kwargs['queryset'] = User.objects.filter(is_staff=True)
        return super(PostAdmin, self).formfield_for_manytomany(
            db_field, request, **kwargs)

В итоге в форме редактирования новости в админке в качестве автора можно будет указать только пользователя со статусом Персонал (Staff), но для фильтра списка новостей будут выведены все пользователи системы.

Из документации по админке Django сказано, что в list_filter можно указывать только название поля:
Set list_filter to activate filters in the right sidebar of the change list page of the admin. This should be a list of field names, and each specified field should be either a BooleanFieldCharFieldDateFieldDateTimeFieldIntegerField or ForeignKey.
Fields in list_filter can also span relations using the __ lookup:
Но если посмотреть код в django.contrib.admin.views.main.ChangeList, метод get_filters() то увидим следующее:
...
filter_specs = []
if self.list_filter:
    for list_filter in self.list_filter:
        if callable(list_filter):
            # This is simply a custom list filter class.
            spec = list_filter(request, lookup_params,
                self.model, self.model_admin)
        else:
            ...

Оказывается в list_filter можно указать "какой-то" класс, экземпляр, которого будет создан для фильтрации. Классы эти можно увидеть вот тут:  django.contrib.admin.filters.

To be continued...