четверг, 10 ноября 2011 г.

Как сохранить видео с Youtube, Vimeo и любого Flash проигрователя

Если вкратце:

for pid in `ps aux | grep flashplugin | grep -v grep | awk '{print $2}'`;
do
FD_DIR=/proc/$pid/fd
for fd in `ls -l $FD_DIR | grep '/tmp/Flash' | grep -o '[0-9]* ->' | grep -o '[0-9]*'`;
do
FD=$FD_DIR/$fd
cp $FD `ls -l $FD | grep -o 'Flash[^ ]*'`.flv;
done;
done

Копируете строки приведенные выше в консоль и нажимаете Enter. Всё видео проигрываемое во Flash плеере в момент исполнения команды будет скопировано в текущий каталог с именами вида: Flash(что-то).flv

Раньше все видео проигрываемые Flash плеером хранились в /tmp и его можно было просто скопировать. Но с некоторых пор плеер удаляет файлы с видео из /tmp сразу после создания. В Linux данные файлов по настоящему не удаляются пока их не прекратят использовать все программы, но по оригинальному имени к файлу обратится уже нельзя. Через директорию /proc можно получать информацию о процессах, в частности, получить доступ ко всем файлам открытым программой, даже удаленным.

Адрес файла будет таким:
/proc/{PID процесса}/fd/{Дескриптор файла}

PID процесса можно узнать с помощью команды ps или любого менеджера процессов.

Нужный дескриптор файла можно определить выполнив:
ls -l /proc/{PID процесса}/fd

В выводе этой команды будут оригинальные имена файлов включая удаленные.

воскресенье, 12 декабря 2010 г.

Как отключить (авто)монтирование флешек в Linux

Несколько новых способов работающих на новых дистрибутивах:

Способ № 1.
Для отключения:
chmod 0700 /media

Для включения:
chmod 0755 /media

Суть этого способа в том, что бы запретить пользователю читать данные с флешки, cd/dvd-диска и т.д. Большинство современных дистрибутивов с включённым автомонтирование монтируют все сменные устройства в директорию /media, поэтому запретив доступ к ней, запрещаем доступ ко всему ее содержимому.
Этот способ удобен, если нужно на одном и том же компьютере кому-то разрешить, а кому-то запретить доступ к внешним устройсвам хранения. В примере единственный пользователь, который может писать и чатать данные с флешки это root (если владелец и группа каталога /media - root).


Если сменить группу каталогу /media например на "storage" и сменить права на /media:
chmod 0750 /media
Только пользователи из группы "storage" смогут получить доступ к сменным носителям. (По поводу групп это все в теории так на практике не проверял)


P.S. Само устройство будет появляться и монтироватся, но с данными на нем работать будет нельзя.
P.S. CD/DVD диски пишутся НЕ копирование файлов в папку, поэтому запретить запись на них так не получится.


Способ № 2.
Для отключения:
rmmod usb_storage; echo 'blacklist usb_storage' > /etc/modprobe.d/blacklist-storage.conf

Для включения:
rm /etc/modprobe.d/blacklist-storage.conf; modprobe usb_storage

Здесь просто выгружается драйвер флешки и выключается из автоподгрузки при вставке устройства. В этом случае ни один пользователь за компьютером не сможет воспользоваться флешкой, но и сообщений о готовности устройства, как в первом способе, появляться не будет.

P.S. Если правильно помню, /etc/modprobe.d есть не во всех дистрибутивах, так что возможно потребуются незначительные изменения.

Все команды нужно выполнять от root. Тестировалось на Ubuntu 10.10.


ВНИМАНИЕ! все ниже описанное не работает с новым udev, он запрещает пустое имя устройства. Оставлено, так как может содержать полезную информацию.


От 13.12.08.

Нужно всего лишь выполнить команду:

Для отключения:
echo 'ATTRS{removable}=="1", SUBSYSTEMS=="block", NAME=""' > /etc/udev/rules.d/99-block-storage.rules

Или(если используется sudo):
sudo sh -c 'echo "ATTRS{removable}==\"1\", SUBSYSTEMS==\"block\", NAME=\"\"" > /etc/udev/rules.d/99-block-storage.rules'


Для включения:
rm /etc/udev/rules.d/99-block-storage.rules
Или закоментировать содержимое этого файла.

Разумеется все от root.
Перезагружаться, или что-то перезапусткать не нужно.

Теперь немного о том зачем все это нужно, и, что здесь все таки происходит.
Иногда, к примеру в организациях, нужно запретить сотрудникам использовать съемные носители данных(флешки, карточки, жесткие диски и т.д.). Конечно можно просто отключить USB в BIOS, но если к компьютеру подключен принтер, сканер, мышь или еще что, то задача усложняется.

Все решается просто, благодаря udev. Если коротко, то это подсистема Linux, которая отвечает за появление новый файлов устройств. Для нас важно, что можно писать правила для этих устройств.

Так выглядит наше правило:
ATTRS{removable}=="1", SUBSYSTEMS=="block", NAME=""

ATTRS{removable}=="1"
Нужны только отключаемые устройства (что бы жесткий диск случайно не отключить).

SUBSYSTEMS=="block"
Нас интересуют устройства хранения данных. (Я пробовал другие подсистемы и драйвера, но в этом случае отдельные партиции все равно подключались).

NAME=""
Собственно действие. Не создавать файл устройства.

Минусы данного подхода:

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

  • Приведенное правило, теоретически, может совпасть с каким-нибудь устройством, которое использует SUBSYSTEMS=="block", но при этом не является устройством хранения. В этом случае в правило нужно добавить исллючение, об этом хорошо написано здесь.


Все выше описанное протестировано на Ubuntu 8.10, но должно работать на любом современном дистрибутиве.

Если подняться на уровень HAL, то открываются очень интересные возможности, как к примеру выбор пользователей кому,что и когда можно подключать. А если сюда еще замешать SELinux и/или FUSE то остановить ограничение пользователей может только фантазия :)

воскресенье, 13 июня 2010 г.

Заполнение PDF форм

Нужно было написать не большую программку, смысл которой: Много операторов вводят данные, один проверяет введенное и печатает отчеты заданного образца. Шаблоны этих отчетов мне передали некорректно сверстанные, в формате DOC. Следовательно, что бы заполнять их данными через програму нужно их переверстать (если оставить как есть, то шаблоны разваливаютсь от малейшего изменения). На верстку ни сил, ни времени, ни желания, разумеется не было.

PDF это первое, что мне приходит на ум, когда я слышу "печать документов", Поэтому я перевел все шаблоны из DOC в PDF, при помощи замечательной кнопочки в OpenOffice. Теперь по заполнению. На базе PDF можно создавать формы, которые потом заполняются либо руками, либо программно.
Только что узнал, что OpenOffice и сам умеет создавать PDF формы, достаточно добавить поля форм на страницу и экспортировать в PDF. Но в тот момент я об этом не подумал (мне должно быть стыдно!), поэтому пошел в обход и воспользовался Scribus. Импортировал в него созданные ранее PDF, как фоновые изображения и наложил сверху слой с элементами формы, потом опять экпортировал в PDF.
Вывод из этой истории: можете попробовать создать форму прямо в OpenOffice, а если не получится, или результат не будет Вас устраивать, воспользуйтесь Scribus.

Формы готовы, теперь о том, как их все таки заполнять. Моя программа написана на базе Django, поэтому я искал какое-нибудь Python ориентированное решение. Многим известный Reportlab надежд не оправдал, так как полноценная работа с существующими PDF в open source версии отсутсвует. После затяжных поисков я начал посматривать на консольные программки, которыми можно вопользоватся через os.system. Не красивое решение, но нужно было хоть что-то. Как оказалось pdftk умеет заполнять PDF формы! Ура! однако радость была не долгой ... сервер с програмкой работает под управлением 64 битной FreeBSD, но собрать pdftk под эту платформу за разумное время мне так и не удалось, а тесты на Linux показали, что с кодировками и шрифтами тоже не все гладко.

Небольшое лирическое отсупление, о том как все таки заполняют PDF формы. Алгоритм такой:
1. Сначало создается XFDF или FDF файл содержащий имена полей и что в них должно быть (от себя добавлю, забудьте про FDF это жуткий формат, пользуйтесь XFDF, который по сути обычный XML)
Формат XFDF выглядит так:
<?xml version="1.0" encoding="UTF-8"?>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<fields>
<field name="ИМЯ_ПОЛЯ">
<value>ЗНАЧЕНИЕ_ПОЛЯ</value>
</field>
</fields>
</xfdf>

2. Потом XFDF/FDF файл и PDF с формой передаются программе, которая читает данные из первого и заносит их во второй.

После возобновления поисков был найден iTextApple отношения не иммет), который, судя по описанию, умел все что нужно и его разработка шла полным ходом (в отличии от заброшенного pdftk). Однако, у этого чудо техники есть две важные особенности:
1. Это не полноценная программа, а библиотека для работы с PDF.
2. Она написана на Java ...

В связи с тем, что на очередные поиски времени не было, решил написать свою первую программу на Java.

Вот что получилось:
Скомпилированная программа: xfdffill.jar
Исходный код: XFdfFill.java
package org.fillpdf;
import java.io.*;

import com.itextpdf.text.pdf.*;

public class XFdfFill {

public static void main(String[] args) {
if (args.length != 3){
System.out.println("usage:");
System.out.println("xfdffill <out.pdf> <form.pdf> <data.xpdf>");
return;
}

try {
PdfReader pdfreader = new PdfReader(args[1]);

PdfStamper stamp = new PdfStamper(pdfreader, new FileOutputStream(args[0]));
XfdfReader fdfreader = new XfdfReader(args[2]);
AcroFields form = stamp.getAcroFields();
form.setFields(fdfreader);
stamp.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

пользоваться так:
java -jar xfdffill.jar filledform.pdf pdfform.pdf data.xfdf

Где:
filledform.pdf - Файл в котором будет заполненая PDF форма.
pdfform.pdf - Оригинальная PDF форма.
data.xfdf - XFDF файл с данными.

Далее, после успешного заполнения формы отправляю получившийся файл оператору.

Но это еще не все. Некоторые документы должны содержать внутри себя много копий одного шаблона с несколькими измененными значениями полей. Есть шаблон его нужно заполнить N количество раз и каждый N раз меняется только одно - два поля, при этом оператору нужно вывести на печать все эти документы сразу. Заставлять оператора загружать несколько файлов - это не удобно, поэтому нужно объединить все эти почти одинаковые документы в один документ и отдать оператору один PDF.

Писать больше ничего не хотелось, поэтому попробовал программы, которые объединяют несколько PDF в один. К сожалению ни одна из них не справилась с PDF формой (В моем случае нужно было, что бы форма отставалась редактируемой для оператора). Поэтому опять взялся за IText.

Получилось следующее:
Скомпилированная программа: pdfmerge.jar
Исходный код: PdfMerge.java
package org.pdfmerge;
import java.io.*;
import java.util.*;

import com.itextpdf.text.pdf.*;

public class PdfMerge {

public static void main(String[] args) {
if (args.length < 2){
System.out.println("usage:");
System.out.println("pdfmerge <out.pdf> <in1.pdf> [ ... <inN.pdf> ]");
return;
}
try{

PdfCopyFields copy = new PdfCopyFields( new FileOutputStream(args[0]));
PdfReader reader;
for( int i = 1; i < args.length; i++){
reader = new PdfReader(args[i]);
List<Integer> pages = new ArrayList<Integer>();
int allpages = reader.getNumberOfPages() + 1;
for (int p = 0; p < allpages; p++){
pages.add((Integer)p);
}
copy.addDocument(reader, pages);
}
copy.close();
}catch (Exception e) {
e.printStackTrace();
}

}

}

Пользоваться так:
java -jar pdfmerge.jar out.pdf in1.pdf in2.pdf ...

Где:
out.pdf - Файл в котором будут все PDF.
in1.pdf in2.pdf ... inN.pdf - Файлы которые нужно объединить

Эта программа хорошо справляется с файлами, которые сгенерированы IText (в частности получеными от xfdffill). Если в форме были заполненые поле не через IText (я пробовал через Scribus) то вместо значений будут кракозябры.

Принцип работы такой, заполняю одну и ту же форму с помощью xfdffill разными данными полученные pdf файлы объединяю в один с помощью pdfmerge и то что получилось отправляю оператору.
В получившемся документе есть поля с одинаковыми именами и при редактировании одного из полей данные меняются во всех полях (в моем случае это не критично, а иногда даже полезно).

И напоследок класс на Python для генирации XFDF файлов.
Исходный код: xfdfwriter.py
# -*- coding: utf-8 -*-
from xml.sax.saxutils import escape, quoteattr

class XFDFWriter():

def __init__(self, xfdf_name=None):
'''
        xfdf_name имя файла, куда нужно
        поместить результат
        '''

self.fields = {}
self.xfdf_name = xfdf_name

def setField(self, name, value):
'''
        Устанавливаем значение поля
        
        Эту функцию можно вызывать много раз.
        после каждого вызова старое значение поля заменяется новым
        '''

self.fields[name] = value

def setFields(self, names, value):
'''
        Устанавливаем одно и то же значение многим полям.

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

for name in names:
self.setField(name, value)

def write(self, xfdf_name=None):
'''
        Записываем результат в файл.

        Если xfdf_name не задан, то используем значение
        установленное в конструкторе.
        Указывать xfdf_name в этой функции удобно,
        если нужно создать несколько xfdf файлов, в которых
        отличаются значения нескольких полей, а в основном значения
        полей одинаковы.
        '''

data = u'<?xml version="1.0" encoding="UTF-8"?>\n'
data += u'<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">\n'
data += u' <fields>'

for field in self.fields:
data += u'\n <field name=%s><value>%s</value></field>'%(quoteattr(field), escape(self.fields[field]))

data += '\n</fields>'
data += '\n</xfdf>\n'

if not xfdf_name:
xfdf_name = self.xfdf_name

xfdf_file = open(xfdf_name, 'w')
xfdf_file.write(data.encode("utf-8"))
xfdf_file.close()


Ниже приведу пример использования этого класса.

# -*- coding: utf-8 -*-

#Подключаем XFDFWriter

from xfdfwriter import XFDFWriter

# Создаем экземпляр XFDFWriter который будет писать
# данные в 'file.xfdf'

xfdf = XFDFWriter('file.xfdf')

# Устанавливаем значения полей 'name', 'year_start', и 'year_end'

xfdf.setField('name', u'Проект №1')
xfdf.setField('year_start', u'2000 г.')
xfdf.setField('year_end', u'2003 г.')

# Записываем xfdf в файл 'file.xfdf' указанный при
# создании экземпляра этого класса

xfdf.write()

# Переопределяем значение поля 'year_start'
# (значение полей 'name' и 'year_end' остается прежним)

xfdf.setField('year_start', u'2001 г.')

# Записываем новый вариант в файл file1.xfdf'

xfdf.write('file1.xfdf')

# Переопределяем значение полей 'year_start' и 'year_end'
# (значение поля 'name' остается прежним)

xfdf.setFields( ['year_start','year_end' ], u'2002 г.')

# Записываем новый вариант в файл file2.xfdf'

xfdf.write('file2.xfdf')


P.S. Лицензия на Java программки такая же как и на IText т.е. AGPL.
Лицензия на Python программки public domain т.е. какая хотите :)

P.P.S. Использовать java через os.system очень неправильно, когда будет минутка объединю эти две программки в одного демона, который будет слушать pipe, а пока пользуюсь тем, что оператор выводящий документы только один, и в моем распоряжении мощный сервер.

понедельник, 7 декабря 2009 г.

Установка индивидуальных deb пакетов с автоматическим удовлетворением зависимостей

Ситуация:
Из источников, близких к сомнительных, нам достался deb пакет (например Skype, Opera, MySQL Workbench ...). Возникает желание поставить его быстро, а не заниматься удовлетворением зависимостей.

Как выяснилось, все очень просто:

dpkg -i имя_пакета.deb || apt-get -f install

или с sudo:

sudo dpkg -i имя_пакета.deb || sudo apt-get -f install

Теперь о том, что здесь происходит.
Оказывается dpkg -i сначала распаковывает пакет и регистрирует его в системе, и лишь потом проверяет удовлетворены ли все зависимости этого пакета. Получается, что пакет уже как бы установлен но в состоянии "not fully installed or removed" как отзывается о нем apt-get.

Ключ -f для apt-get говорит о том, что apt-get'у надо поправить все несуразности в базе пакетов т.е. установит все, что недоустановлено и удалить то, что недоудалено.

P.S. А самое удивительное во всей этой истории то, что до этого метода я додумался только сейчас!

понедельник, 2 ноября 2009 г.

Навигация по видео презентации

Недавно понадобилось представить свою разработку (и немного попиарить Linux). Захотелось произвести впечатление, поэтому нарисовал презенташку в Blender на выходе, разумеется получился видео ролик. Встал интересный вопрос о переходах между логическими частями фильма (читать слайдами). Ниже привожу один из способов.

В моем распоряжении был ноутбук с KDE4, последним я и воспользовался точнее многогранным плазмоидом Folder View.

Я хотел получить следующее:
Миниатюры слайдов расположенные в порядке следования.
При нажатии на миниатюру плеер переходит на соответствующее место.
Возможность вызвать список миниатюр не нарушая работы плеера.

Для простоты добавим еще один активити, а по центру разместим плазмоид Folder View. В настройках Folder View укажем для показа папку ~/presentation/control/ (имя не важно, но я назвал ее так) в ней будут находится desktop файлы, управляющие плеером. В еще одной папке ~/presentation/pic/ позже поместим кадры из ролика, соответствующие разделам.

Должно получится следующее:

#Папка с desktop файлами для Folder View
~/presentation/control/

#Папка с иконками для desktop файлов
~/presentation/pic/

#Скрипт для управления плеером
~/presentation/setslide

#Собственно презентация
~/presentation/presentation.avi

Код скрипта (~/presentation/setslide) для управления плеером:

#! /bin/bash

#Плеер я выбрал dragon потому, что им можно управлять через dbus
#но постоянного адреса на dbus у него нет поэтому здесь получаю текущий
DRAGON=`qdbus | grep dragonplayer`


#Прошлая инструкция сработает только в случае если плеер уже был запущен
#запускаем его если прошлый шаг не удался и снова пробуем получит адрес
if [ -z $DRAGON ]
then
dragon ~/presentation/presentation.avi &
sleep 2
DRAGON=`qdbus | grep dragonplayer`
fi

#Перематываеи видео в плеере до нужного момента
#позицию передаем как аргумент
qdbus $DRAGON /Player org.freedesktop.MediaPlayer.PositionSet $1

#после перемотки плеер автоматически становится на паузу
#если мы хотим продолжение воспроизведения нужно раскомментировать строчку ниже
## qdbus $DRAGON /Player org.freedesktop.MediaPlayer.Play

#Разворачиваем плеер на полный экран
qdbus $DRAGON /dragonplayer/MainWindow_1/actions/fullscreen com.trolltech.Qt.QAction.setOn true



Заполним Folder View слайдами.
В папке ~/presentation/control/ нужно создать desktop файлы в количестве "слайдов" презентации.

Содержимое desktop файла:

[Desktop Entry]
Exec=~/presentation/setslide XXXXXXXX
Icon=/home/user/presentation/pic/X.png
Type=Application
StartupNotify=false



Где XXXXXXXX - это позиция на которую нужно перевести плеер. Что бы узнать это число нужно перейти в dragon на нужную позицию, поставить его на паузу и выполнить команду в консоли:

qdbus `qdbus | grep org.mpris.dragon` /Player org.freedesktop.MediaPlayer.PositionGet


Имена desktop файлов должны соответствовать именам разделов, в моем случае получилось так:

~/presentation/control/Linux
~/presentation/control/Виртуализация
~/presentation/control/Вредоносное ПО
~/presentation/control/Вредоносное ПО в Linux
~/presentation/control/Все равнозначны
~/presentation/control/Начало
~/presentation/control/Не все приложения под Linux
~/presentation/control/Отвлекающие приложения
~/presentation/control/Отсутствие центрального управления
~/presentation/control/Режим киоска
~/presentation/control/Упрощенная оболочка
~/presentation/control/Централизованное управление

Теперь назначим иконки desktop файлам.
Иконки могут быть любыми. Я сделал скриншоты основных моментов каждой части, изменил их размер до 256x256 (это не обязательно) и сохранил в папку ~/presentation/pic/ под номерами в последовательности следования слайдов т.е. 1.png, 2.png, ... 12.png. В соответствующем desktop файле нужно установить значение Icon на соответсвующий файл картинки.
Здесь поджидает неприятность. Иконки в Folder View не будут обновляться. Правится это удалением файлов:

~/.kde/cache-spider/kpc/kde-icon-cache.data
~/.kde/cache-spider/kpc/kde-icon-cache.index

Они отвечают за кеширование иконок в KDE4, эти файлы пересоздадутся автоматически, поэтому удалять их следует после того как будет закончено изменение всех иконок в desktop файлах.

Как это выглядит:


Правильная последовательность настраивается методом перетаскивания иконок в Folder View на нужные места. Нужно заблокировать позиции иконок в настройках Folder View иначе они будут перемешиваться при обновлении и изменении размера плазмоида.

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

Выглядит это так:


Ну и на последок вот презентация, которую я показывал:

Presentation of Linux (and some custom made software) for classrooms from dik123 on Vimeo.


На странице vimeo размер картинки побольше.

И для владельцев смартфонов, которые обычно умеют играть исключитьно YouTube, тоже самое в доступном виде:

или на странице YouTube.