Recortando vídeo com FFmpeg

O FFmpeg é um software bastante completo para trabalhar com vídeo na linha de comando. Aqui coloco um breve tutorial para recortar um vídeo e remover trechos que não são desejados.

Por exemplo, em um vídeo de 30 minutos, eu quero os trechos: 0 a 10 minutos, 11 a 16 minutos e 18 minutos até o fim. Primeiro, é preciso criar os 3 segmentos, aqui chamados de “part%02d.mp4″.

ffmpeg -i video.mp4 -t 00:10:00 -c copy part01.mp4
ffmpeg -ss 00:11:00 -i video.mp4 -t 00:05:00 -c copy part02.mp4
ffmpeg -ss 00:18:00 -i video.mp4 -c copy part03.mp4

Comandos utilizados:
-i ARQUIVO_DE_INPUT
-ss TEMPO_DE_SEEK
-t DURAÇÃO_DO_SEGMENTO

O uso de -c copy faz com que não seja realizada transcodificação do vídeo, mas apenas cópia de arquivo. Entretanto, isso faz com que os cortes não sejam precisos e determinados pelos keyframes do vídeo, ficando possivelmente maiores que o pedido. Se for preciso fazer cortes com precisão, é preciso recodificar o segmento (e mover o comando “-ss” para depois do arquivo de input).

Para juntar os pedaços, crie um arquivo de texto, por exemplo “lista.txt” com o seguinte conteúdo:

file 'part01.mp4'
file 'part02.mp4'
file 'part03.mp4'

Agora é só executar:

ffmpeg -f concat -i lista.txt -c copy video_final.mp4

Funciona com qualquer formato/codec suportado pelo FFmpeg. Se o seu programa não suportar algum dos comandos, provavelmente é uma versão antiga. Utilize 1.0 ou superior.

Criador de Funções com Metaclasses

Esse é um projeto que fiz meses atrás para gerar funções em Python com operadores matemáticos usando metaclasses.
Com isso, você pode fazer:

g = f ** 2 + 1   #função que eleva ao quadrado e soma 1
g(10) == 101

O código abaixo é uma versão (muito) simplificada do módulo que coloquei no GitHub, mas serve como exemplo de uso de metaclasses em Python (neste caso, Python 3):

import operator

class MetaFuncBuilder(type):
    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        attr = '__{0}{1}__'

        for op in (x for x in dir(operator) if not x.startswith('__')):
            oper = getattr(operator, op)
            op = op.rstrip('_') #special case for keywords: and_, or_

            def func(self, *n, oper=oper):
                return type(self)(lambda x: oper(self.func(x), *n),
                                  self.op + [(oper.__name__, n[0])
                                             if n else oper.__name__])
            def rfunc(self, n, *, oper=oper):
                return type(self)(lambda x: oper(n, self.func(x)),
                                  self.op + [(n, oper.__name__)])
           
            setattr(self, attr.format('', op), func)
            setattr(self, attr.format('r', op), rfunc)

class FuncBuilder(metaclass=MetaFuncBuilder):
    def __init__(self, func=None, op=None):
        self.op = op if op else []
        self.func = func if func else lambda x: x

    def __repr__(self):
        return '<var %s>' % self.op

    def __call__(self, *args):
        if not args:
            return self.func() #unary operators
        required, *args = args
        out = self.func(required)
        return out(*args) if args else out

f = FuncBuilder()

Em duas situações é comum o uso de metaclasses em Python: para que um certa classe tenha propriedades novas ou para automatizar algum mecanismo nas classes. Neste caso, usei para automatizar a criação de métodos para cada operador em Python, com ajuda do módulo operator.

Em Python, os operadores matemáticos binários existem nas formas __op__, __rop__ e __iop__, onde op é o nome, r significa que o objeto é o segundo operador e i significa in-place. Outros operadores existem em apenas uma forma.
Assim, o código pega os operadores e cria as três formas de cada um (os que existem em apenas uma forma ficam com 2 formas extras que não causam problema algum, já que não são nunca chamadas) e todos os operadores, ao invés de executar a operação designada, apenas criam uma novo objeto-função que pode ser chamado ou ter novas funções adicionadas.

O módulo no GitHub tem uma centena de outras funcionalidades para gerar essas funções, mas ele sofre de um problema que pode ser bem sério: Chamadas de função em Python são caras!

Basicamente, cada objeto do tipo FuncBuilder guarda a operação que tem que executar e o próximo da lista. Isso gera um monte de chamadas de função em cima dos operadores, o que pode deixar o código muito mais lento.

Minha próxima alteração pra esse código seria juntar todas as operações numa só chamada de função, o que não vai permitir extrair uma função de dentro de outra (como é possível agora por meio de closures), mas vai reduzir o custo para apenas uma chamada de função, como seria natural. Isso não vai ser tão simples por causa dessa estrutura recursiva da classe, mas quando tiver tempo, eu faço.

Otimização 0.1

Achei bem interessante que nesse tempo que fiquei sem escrever, o blog teve uma quantidade de acessos maior que em tempos de atividade. De qualquer forma, um post de vez em quando não faz mal a ninguém :).

Tem um site de tirinhas chamado Vida de Programador que posta situações corriqueiras (as vezes) de forma engraçada.

Na tirinha de hoje, ele mostrava o código de um programador iniciante:

bool isValid;
...
switch(isValid) {
case true: ...
case false: ...
}

Claro que não faz sentido tanta verborragia pra algo tão simples como uma variável booleana. No entanto, as pessoas se esquecem que os compiladores atuais são muito bons e não vão deixar seu código lento só porque você preferiu que ele fosse “mais legível”.

Uma excelente ferramenta online chamada GCC Explorer mostra a saída assembly do seu compilador preferido com diversos níveis de otimização.

Independente do nível de otimização, o código gerado para o trecho acima será extamente o mesmo que para “if...else” ou o operador ternário “? :“.

int testFunction(bool x) {
  switch (x) {
    case true:
        return 10;
        break;
    case false:
        return 12;
  }
}

int testFunction2(bool x) {
  return x? 10 : 12;
}

E o Assembly gerado (com -O2) será:

testFunction(bool):
	cmpb	$1, %dil
	sbbl	%eax, %eax
	andl	$2, %eax
	addl	$10, %eax
	ret
testFunction2(bool):
	cmpb	$1, %dil
	sbbl	%eax, %eax
	andl	$2, %eax
	addl	$10, %eax
	ret

Se você olhar, não há nenhum jump no assembly gerado. Ele compara a variável com o número 1, subtrai o registro de retorno dele mesmo com borrow, faz um AND com o 2 e depois soma 10. Você não ia pensar que retornar 12 ou 10 significa colocar 2 ou 0 numa variável e depois somar 10, mas o compilador sabe que lógica AND e OR para números inteiros é muito mais rápida que jumps e faz tudo sem que você se preocupe. Esse código seria semelhante a:

int testFunction3(bool x) {
  return 10 + (2 & -(1-x));
}

Mas o GCC consegue diminuir ainda mais o assembly!

testFunction3(bool):
	leal	3(%rdi), %eax
	andl	$2, %eax
	addl	$10, %eax
	ret

Só que ninguém vai querer ler um código tão feio assim que apenas poupa 1 nanossegundo.

Sem dar razão à pessoa que fez um switch para um bool, se o código não é o gargalo do seu programa, não há motivo para mudar. Como escreveu Donald Knuth: otimização prematura é a raiz de todo o mal.

Blender 2.60 e Cycles

Há alguns meses eu ouvi falar do Cycles que é um novo renderizador para o Blender 2.6x (versão “estável” do programa depois da grande mudança de interface) e, depois de descobrir que não precisa de uma GPU com CUDA, resolvi testar o seu poder.

O Blender ficou muito mais prático com essa interface, mas é preciso se acostumar com os novos comandos e a posição dos botões e menus. No site Blender Guru tem uma cola dos comandos que é bastante completa.

Sobre o Cycles, ele é um Ray tracer de verdade e produz imagens muito mais reais que o renderizador interno (Internal), além de trazer a possibilidade de editar a cena e produzir imagens ao mesmo tempo.

O modo de funcionamento do Cycles é bem diferente do Internal pois ele não divide a imagens em quadrados e renderiza cada um numa thread. Ele gera a imagem inteira de uma vez e vai aprimorando o resultado a cada iteração, o que é um pouco estranho já que a renderização só acaba após o número de passos que for definido.

Em alguns testes que fiz, de 10 a 50 passos, já é razoável para ver o que está acontecendo na imagem (bom para editar em tempo real), mas para uma boa qualidade de imagem são precisos de 1000 a 4000 passos, algo que pode demorar horas, mesmo para uma cena razoavelmente simples (no meu Core i5).

Pode parecer que o Cycles é uma faca de dois gumes, já que permite visualizar o resultado praticamente em tempo real, mas demora pra produzir uma imagem final com pouca granularidade. Na verdade, o Cycles traz ainda mais um diferencial: a possibilidade de renderizar a imagem via GPU com CUDA/OpenCL, o que pode ter um ganho alto de desempenho, além de deixar a CPU livre para outras tarefas. Pena que minha placa de vídeo é bem ruim (a Intel não aprende mesmo…) e não serve para o propósito.

Abaixo um vídeo do Cycles em ação:

De cara dá pra perceber que a qualidade de imagem é muito maior que se fosse renderizado com o Internal, principalmente quando se pensa em iluminação. Antigamente, você precisaria colocar umas 3 lâmpadas, no mínimo, e fazer centenas de ajustes para conseguir um efeito legal. Agora você pode adicionar apenas um objeto com o material “Emission” e você tem uma fonte de luz que reflete nas superfícies e produz um efeito muito bom.

Outra coisa que sempre foi praticamente impossível de fazer direito no Blender é vidro ou qualquer coisa transparente. No Cycles, ao escolher o material Glass, você tem de cara um vidro de alta qualidade sem precisar fazer qualquer ajuste. Para outros materiais, basta ajustar o índice de refração (IOR) para o valor real do mesmo.

Em cinco minutos coloquei 3 planos, um cubo e um chimpanzé (Suzanne + subsurf) numa cena iluminada por outros dois planos e modifiquei os materiais. Depois de meia hora renderizando em torno de 1000 passos, fiquei com uma imagem que demoraria horas para ajustar a iluminação e ter um efeito parecido.

Não é nenhuma obra de arte, mas mostra como é fácil ter efeitos interessantes sem perder muito tempo.

A única diferença entre o uso do Blender Internal e o Cycles é a janela de edição de materiais que, por enquanto, é um pouco pobre em usabilidade e obriga o uso de Composite Nodes para coisas não básicas. De qualquer forma, não deixa de ser algo bom pois o uso do Node editor não é complicado e acaba sendo prático.

Uma coisa que senti falta é a pré-visualização do material, mas com a renderização em tempo quase real, acaba não sendo tão ruim.

Como o Cycles ainda não está oficialmente incorporado ao Blender, é preciso pegar um build que tenha sido feito recentemente no SVN do projeto para poder usar. No site GraphicAll.org tem vários e só precisa escolher o sistema operacional e arquitetura.

De acordo com o roadmap do Blender, o Cycles deve chegar no Blender 2.61 em dezembro.

Recuperando dados de partições Ext3 e Ext4

Nessa semana tive que recuperar um arquivo deletado acidentalmente num servidor rodando CentOS com sistema de arquivos Ext4.

A fração de segundo depois que você aperta Enter e percebe seu erro, mas é tarde demais, você acabou de excluir um arquivo ou diretório valioso e não existia nenhum backup. Ou talvez você tinha um backup, mas é de um mês atrás…
E em estado de choque que você vê num flash o último mês passar diante de seus olhos e percebe a dor que vai dar para fazer tudo de novo…
Carlo Wood

Recuperar dados de partições Ext3 e 4 é bastante complicado, como explicado com muitos detalhes neste link.

A primeira coisa a fazer é desmontar a partição afetada ou, se não for possível, desligar o computador e usar um LiveCD de alguma distribuição Linux (usei Ubuntu num pendrive) ou colocar o HD dentro de outra máquina.

Para resolver o problema, eu testei sem sucesso duas ferramentas (foremost e scalpel) que fazem análise sequencial dos blocos do HD procurando por padrões conhecidos, algo que demorou uma eternidade para verificar toda a partição de 1TB que eu tinha e “recuperou” centenas de arquivos corrompidos com nomes do tipo “100201234.jpg” e nada do que eu queria.

Talvez o fato da minha partição ser LVM e a real partição Ext4 estivesse dentro do volume lógico os programas não funcionaram, mas de qualquer forma existe uma solução melhor.

Uma ferramenta chamada Extundelete foi quem salvou meu dia. Ela acessa o journal do sistema de arquivos e consegue achar novamente os arquivos com os nomes originais e, no meu caso, até manteve a estrutura de diretórios de onde estava o arquivo.

Pra usar, instale o “Extundelete” pelo gerenciador de pacotes da distribuição usada (para Ubuntu):
# apt-get install extundelete

ou pegue o código fonte do site e compile com
# ./configure
# make
# make install

Para executar, é preciso passar a partição a ser usada para que o programa monte em modo read only (se for LVM, como no meu caso, vai ser necessário seguir mais alguns passos).
# extundelete /dev/nome_da_particao --restore-all

Em menos de um minuto, o comando acima restaurou centenas de arquivos, incluindo o que eu queria.

Escolhendo o canal do Wi-Fi

Algo muito comum é instalar um roteador Wi-Fi num ambiente pequeno e, ainda assim, ter problemas com a velocidade da conexão.

Pode acontecer de existirem vários outros roteadores de vizinhos usando o mesmo canal e possivelmente com um sinal mais forte (aquele povo que compra antenas enormes na Uruguaiana ou Santa Ifigênia) estarem fazendo a sua placa wireless perder muitos pacotes.

Todo roteador Wi-Fi vem com uma página de configuração que permite escolher o canal da faixa de 2.4GHz em que vai operar.
Caso não se lembre como acessar ou se não foi você quem instalou, provavelmente olhando qual o gateway da sua máquina você acha o IP do roteador (algo como 192.168.0.1).

Na hora de escolher o canal ideal para sua conexão, é preciso saber que parte da banda os roteadores mais próximos estão localizados e, para isso, existe um excelente aplicativo gratuito chamado InSSIDer que fornece informações bem detalhadas.

Em uma das abas do programa, ele mostra os canais das redes ao alcance, assim como suas intensidades. Ele também mostra a intensidade ao longo do tempo além de outras informações como fabricante, velocidade da rede, largura do canal etc.

Assim, é fácil escolher o canal do seu Wi-Fi apenas verificando qual está menos utilizado (geralmente as extremidades).

Outra coisa é, se você tem um Wi-Fi 802.11n de 300mpbs (bastante comum nos roteadores de 2011) ele pode ocupar o dobro da banda no espectro por utilizar 2 canais, o que melhora a qualidade geral da sua internet, mas causa mais interferência na dos outros :).

Esse é um programa que vale a pena ter instalado.

Python 3 – exec e outras mudanças

Já tem um tempo que tento usar somente Python 3 para meus projetos em Python, mas, como muita gente ainda está presa em versões antigas (como o pessoal do CentOS que há um mês mudou do Python 2.4 para o ainda defasado 2.6), sou obrigado a trabalhar com os dois ambientes.

Portar pequenos programas de uma versão para outra pode ser bastante simples (como no Txt2Tags que precisei de umas 2 ou 3hs para fazer funcionar) quando já se conhece os possíveis problemas e suas soluções, mas nem tudo está bem documentado.

Claro que tudo isso seria muito mais difícil se não houvesse script chamado 2to3.py que vem junto com as versões mais novas do interpretador para automatizar o processo. O problema que existem coisas que ele é incapaz de traduzir ou que precisam de mudanças mais bruscas no código para que funcionem.

Por algum motivo, o site “Dive into Python 3” que tinha uma versão online do livro homônimo foi removido do ar e perdi a melhor referência para o tópico que existia, ** Encontrei um novo mirror do Dive into Python 3 **

Também existem outros bons textos por ai.

Algumas das mudanças mais importantes na nova versão são os iteradores usados em lugares que antes retornavam listas, o uso de unicode por padrão (que na minha opinião já vale por todo o trabalho de portar código) e a troca de algumas palavras-chaves (statements) por funções e vice-versa.

Sobre essa última mudança, as palavras-chave tem o poder de mudar o local em que se encontram enquanto outros nomes podem significar qualquer coisa que o programador queira e só podem ser verificados em tempo de execução, não podendo transformar o ambiente.

Por exemplo, o comando yield faz com que o a função que o contenha seja transformada num gerador mesmo que ele nunca seja executado ou tenha bytecode para isso!

def gerador():
    if 0:
        yield

Neste código acima, o interpretador vai ver que o if nunca será executado e não vai gerar bytecode para nada dentro dele (no caso o yield), mas a função não vai retornar o implícito None quando for chamada. Ela vai retornar um gerador vazio que pode ser usado num loop for.

Usando o módulo dis para desmontar bytecode vemos que a função só retorna None, mas ela vai ter um flag para mostrar sua utilidade:

>>> dis.dis(gerador)
  2           0 LOAD_CONST               0 (None)
              3 RETURN_VALUE

Ainda sobre isso, True, False e None agora são palavras reservadas e que não podem ser modificadas, fazendo com que, se o exemplo acima fosse feito com False ao invés de 0, ele geraria bytecode no Python 2, mas não no Python 3!

No sentido contrário, as palavras-chave print e exec do Python 2 agora são funções e não podem mais alterar seu ambiente.

No caso do print, não tem problema algum e a sintaxe ficou bem melhor, mas o exec (que normalmente seu uso não é uma boa prática de programação) pode ter um comportamento bem diferente nas duas versões do interpretador.

Essa informação não vai ser encontrada na documentação do Python (apenas alguns avisos que não explicam bem a situação) e geralmente a pessoa vai procurar saber disso quando bater de cara com esse problema e nada funcionar.

Olhe o código abaixo (Não tome como exemplo de boa programação):

a = 1
def f():
    exec("a = 2")
    print(a)

f()
print(a)

No Python 2, a função vai imprimir “2” e o outro print vai imprimir “1”, como esperado. No Python 3, o número “1” vai ser impresso nos dois casos.

O que aconteceu?

O meu amigo dis mostra como a variável “a” é chamada no bytecode para impressão dentro da função:

Python 2:

3            8 LOAD_NAME                0 (a)

Python 3:

3           13 LOAD_GLOBAL              2 (a)

Como o Python 3 não consegue ver nenhum a dentro da função, ele assume que é uma palavra definida globalmente e vai embora. No Python 2, é percebida a existência da palavra exec e transforma a busca por um nome sendo primeiro no namespace local e, se não encontrar, no namespace global.

O que, por um lado traz o resultado “desejado”, também faz todos os acessos a funções e variáveis serem mais lento.

Soluções:
1 – Não usar exec.

Praticamente sempre é possível fazer uma solução sem exec que seja simples, mas se for necessário, é preciso tomar cuidado com essa mudança ou passar um segundo parâmetro para a função dizendo em que namespace você quer se o resultado seja avaliado e usar a informação nele.

Normalmente, ninguém usa exec para situações como a que eu mostrei, mas para gerar conteúdo (funções, classes, subclasses, etc) em tempo de execução e muitas vezes existem ferramentas melhores, como as metaclasses e os decoradores.

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.