#!/usr/bin/python
# coding: utf-8

#--------------------------------------------------------------------
# Fichero:  video.py
# Objetivo: Preparar imágenes para crear un vídeo
# Autor:    Pedro Reina <pedro@pedroreina.net>
# Fecha:    S.1.5.2021
# Licencia: Dominio público
#           https://creativecommons.org/publicdomain/zero/1.0/
#--------------------------------------------------------------------

#------------------------------------------------
# Constantes que debe adaptar el usuario
#------------------------------------------------

# La ruta al ejecutable "peano"
EjecutablePeano = '/home/pedro/bin/peano'

#------------------------------------------------
# Constantes que puede adaptar el usuario
#------------------------------------------------

# El carácter para comenzar los comentarios
# en los archivos de definición de vídeos
Comentario = '#'

#------------------------------------------------
# Módulos
#------------------------------------------------

# Leer la línea de órdenes
import sys

# Ejecutar órdenes del sistema
# Comprobar si existe un fichero
# Crear directorios
# Crear vínculos simbólicos en UNIX
# Averiguar el nombre sin extensión de un archivo
import os

# Eliminar directorios con contenido
import shutil

# Obtener números aleatorios
# Revolver una lista aleatoriamente
# Elegir aleatoriamente elementos de una lista
import random

# Pi, seno, coseno, tangente y paso a radianes
import math

# Para medir el tiempo que tarda el programa en ejecutar
# la generación de imágenes
import time

#------------------------------------------------
# Constantes
#------------------------------------------------

# La versión del programa
Version = '2021-05-01'

#------------------------------------------------
# Variables globales
#------------------------------------------------

#------------------------------------------------
# Las variables globales que controlan las palabras
# clave que se pueden usar en los archivos de
# definición de vídeo
#------------------------------------------------

# Las acciones que reconocemos
AccionValida = ('fps', 'anchura', 'altura', 'foto', 'colo', 'troz', 'movi',
    'tran', 'efec')

# Los efectos que reconocemos
EfectoValido = ('difuminar', 'bn')

# Los valores que admiten los parámetros de las transiciones
# dosrec, dosban y dosdia
ParamDosDif = ('mezcla', 'aleatorio', 'mosaico',
    'rec-df', 'rec-fd',
    'cua-df', 'cua-fd',
    'cir-df', 'cir-fd',
    'luz-c', 'luz-o',
    'cor-ns', 'cor-sn', 'cor-eo', 'cor-oe',
    'emp-ns', 'emp-sn', 'emp-oe', 'emp-eo',
    'uves-ns', 'uves-sn', 'uves-oe', 'uves-eo',
    'bv-ns', 'bv-sn', 'bv-oe', 'bv-eo',
    'bh-ns', 'bh-sn', 'bh-oe', 'bh-eo',
    'tri-ns', 'tri-sn', 'tri-eo', 'tri-oe',
    'aba-c', 'aba-n', 'aba-s', 'aba-e', 'aba-o',
    'aba-no', 'aba-ne', 'aba-so', 'aba-se',
    'qui-s', 'qui-n', 'qui-e', 'qui-o', 'qui-v',
    'qui-se', 'qui-ne', 'qui-so', 'qui-no',
    'qui-ns', 'qui-sn', 'qui-eo', 'qui-oe',
    'pon-s', 'pon-n', 'pon-e', 'pon-o', 'pon-v',
    'pon-se', 'pon-ne', 'pon-so', 'pon-no',
    'pon-ns', 'pon-sn', 'pon-eo', 'pon-oe',
    'apa-c', 'apa-n', 'apa-s', 'apa-o', 'apa-e',
    'apa-no', 'apa-ne', 'apa-so', 'apa-se',
    'des-c', 'des-n', 'des-s', 'des-o', 'des-e',
    'des-no', 'des-ne', 'des-so', 'des-se',
    'cas-ns', 'cas-sn', 'cas-oe', 'cas-eo',
    'cas-nose', 'cas-seno', 'cas-neso', 'cas-sone',
    'man-a', 'man-c', 'man-n', 'man-s', 'man-e', 'man-o',
    'man-no', 'man-ne', 'man-so', 'man-se')

# La información sobre los parámetros que admite cada transición
# Es un diccionario y las claves son los nombres de las transiciones
# El valor asociado a cada clave es una tupla, que tiene un dato por cada
#     parámetro admitido (vacía, por tanto, si no admite ninguno)
# Cada dato de la tupla es un diccionario, con estas claves:
#   'funcion': si es necesaria una función que se llame como la transición y
#              además el primer parámetro (True) o solo la transición (False)
#   'tipo': si se espera un texto o un número
#   'valor': si se espera un texto, los valores válidos
#            si se espera un número, el nombre de la función que lo valida
InfoParamTran = {
    'mezcla': (),
    'luz': (
        {'funcion': False, 'tipo': 'texto', 'valor': ('claro', 'oscuro')},
        ),
    'cortina': (
        {'funcion': False, 'tipo': 'texto', 'valor': ('ns', 'sn', 'eo', 'oe')},
        ),
    'empuja': (
        {'funcion': True, 'tipo': 'texto', 'valor': ('ns', 'sn', 'eo', 'oe')},
        ),
    'rectangulo': (
        {'funcion': False, 'tipo': 'texto', 'valor': ('df', 'fd')},
        ),
    'aparece': (
        {'funcion': False, 'tipo': 'texto',
         'valor': ('c', 'no', 'n', 'ne', 'o', 'e', 'so', 's', 'se')},
        ),
    'desaparece': (
        {'funcion': False, 'tipo': 'texto',
         'valor': ('c', 'no', 'n', 'ne', 'o', 'e', 'so', 's', 'se')},
        ),
    'aleatorio': (
        {'funcion': False, 'tipo': 'numero', 'valor': 'Divide'},
        ),
    'mosaico': (
        {'funcion': False, 'tipo': 'numero', 'valor': 'Divide'},
        ),
    'cuadrados': (
        {'funcion': False, 'tipo': 'texto', 'valor': ('df', 'fd')},
        {'funcion': False, 'tipo': 'numero', 'valor': 'Divide'}
        ),
    'circulos': (
        {'funcion': False, 'tipo': 'texto', 'valor': ('df', 'fd')},
        {'funcion': False, 'tipo': 'numero', 'valor': 'Divide'}
        ),
    'cascada': (
        {'funcion': False, 'tipo': 'texto',
         'valor': ('ns', 'sn', 'eo', 'oe', 'nose', 'seno', 'neso', 'sone')},
        {'funcion': False, 'tipo': 'numero', 'valor': 'Divide'}
        ),
    'mancha': (
        {'funcion': False, 'tipo': 'texto',
         'valor': ('a', 'c', 'n', 's', 'e', 'o', 'no', 'ne', 'so', 'se')},
        {'funcion': False, 'tipo': 'numero', 'valor': 'Divide'}
        ),
    'quitar': (
        {'funcion': True, 'tipo': 'texto',
         'valor': ('s', 'n', 'e', 'o', 'se', 'ne', 'so', 'no',
                   'ns', 'sn', 'eo', 'oe', 'v')},
        ),
    'poner': (
        {'funcion': False, 'tipo': 'texto',
         'valor': ('s', 'n', 'e', 'o', 'se', 'ne', 'so', 'no',
                   'ns', 'sn', 'eo', 'oe', 'v')},
        ),
    'bandasvert': (
        {'funcion': False, 'tipo': 'texto', 'valor': ('ns', 'sn', 'oe', 'eo')},
        {'funcion': False, 'tipo': 'numero', 'valor': 'DivideAnchura'}
        ),
    'bandashori': (
        {'funcion': False, 'tipo': 'texto', 'valor': ('oe', 'eo', 'ns', 'sn')},
        {'funcion': False, 'tipo': 'numero', 'valor': 'DivideAltura'}
        ),
    'abanico': (
        {'funcion': False, 'tipo': 'texto',
         'valor': ('c', 'no', 'n', 'ne', 'o', 'e', 'so', 's', 'se')},
        {'funcion': False, 'tipo': 'numero', 'valor': 'Natural'},
        {'funcion': False, 'tipo': 'texto', 'valor': ('pos', 'neg')}
        ),
    'peano': (
        {'funcion': False, 'tipo': 'numero', 'valor': 'Divide'},
        {'funcion': False, 'tipo': 'texto',
         'valor': ('no', 'n', 'ne', 'o', 'e', 'so', 's', 'se', 'c')}
        ),
    'triangulo': (
        {'funcion': False, 'tipo': 'texto', 'valor': ('oe', 'sn', 'eo', 'ns')},
        {'funcion': False, 'tipo': 'numero', 'valor': 'AnguloValido'},
        ),
    'uves': (
        {'funcion': False, 'tipo': 'texto', 'valor': ('oe', 'sn', 'eo', 'ns')},
        ),
    'dosrec': (
        {'funcion': False, 'tipo': 'numero', 'valor': 'MenorQueAnchura'},
        {'funcion': False, 'tipo': 'texto',  'valor': ParamDosDif},
        {'funcion': False, 'tipo': 'texto',  'valor': ParamDosDif}
        ),
    'dosban': (
        {'funcion': False, 'tipo': 'numero', 'valor': 'MenorQueAltura'},
        {'funcion': False, 'tipo': 'texto',  'valor': ParamDosDif},
        {'funcion': False, 'tipo': 'texto',  'valor': ParamDosDif}
        ),
    'dosdia': (
        {'funcion': False, 'tipo': 'texto',  'valor': ParamDosDif},
        {'funcion': False, 'tipo': 'texto',  'valor': ParamDosDif}
        ),
    }

#------------------------------------------------
# Las variables globales que utiliza el programa
# para su funcionamiento interno
#------------------------------------------------

# El directorio en el que dejamos los vínculos a las imágenes finales
DirFinal = 'final'

# El archivo que sirve de testigo para saber si una transición está
# completamente calculada
Testigo = 'testigo'

# El nombre del proyecto: el nombre del archivo sin extensión
Clave = None

# El directorio en el que programa escribirá todo lo que necesite
# Cualquier dato que haya que crear, se creará aquí dentro
# En principio, usaremos el mismo nombre que la clave
Dir = None

# Los cuadros (frames) por segundo del vídeo
FPS = None

# Dimensiones del vídeo
Anchura = None
Altura = None

# Si todo está bien en la configuración
TodoBien = True

# La lista de acciones que van generando un vídeo
ListaAccion = []

# La información que está pendiente de completar o utilizar
# Solo la modifica la función Continuidad()
Pendiente = {'accion': None, 'imagen': None, 'descripcion': None}

#------------------------------------------------
# Funciones
#------------------------------------------------

#------------------------------------------------
# Las funciones que generan las órdenes para las transiciones,
# que reciben el mismo nombre que la propia transición añadiendo
# el parámetro con una separación de un carácter subrayado
#------------------------------------------------

#------------------------------------------------
def mezcla(Info):
    # Tomamos las variables del argumento
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']

    # Preparamos la orden
    Orden = ('composite -blend ' + str(Porcentaje) + '% ' + Anterior +
             ' ' + Siguiente + ' ' + Nueva)

    # La devolvemos
    return Orden

#------------------------------------------------
def luz(Info):
    # Tomamos las variables del argumento
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Modo = Info['param1']

    # El archivo que contendrá la máscara
    Mascara = DirAux + '/mascara.png'

    # Si nos piden el modo oscuro, invertimos el porcentaje
    if Modo == 'oscuro':
        Porcentaje = 100 - Porcentaje

    # La orden para crear la máscara para componer las imágenes
    OrdenMascara = ('convert ' + Anterior + ' -threshold ' +
                    str(Porcentaje) + '% ' + Mascara)

    # Si nos piden el modo oscuro, invertimos la máscara
    if Modo == 'oscuro':
        OrdenNegacion = 'convert ' + Mascara + ' -negate ' + Mascara

    # La orden para componer la nueva imagen
    OrdenComponer = ('convert ' + Anterior + ' ' + Siguiente + ' ' + Mascara +
                     ' -composite ' + Nueva)

    # Devolvemos la orden
    if Modo == 'oscuro':
         return OrdenMascara + ';' + OrdenNegacion + ';' + OrdenComponer
    else:
         return OrdenMascara + ';'+ OrdenComponer

#------------------------------------------------
def cortina(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Sentido = Info['param1']

    # El archivo que contendrá la máscara
    Mascara = DirAux + '/mascara-' + Formato4(Paso) + '.png'

    # Vemos si ya ha sido creado anteriormente
    ExisteMascara = os.path.exists(Mascara)

    # Calculamos las coordenadas superior izquierda e inferior derecha
    # del rectángulo que ocupa la imagen anterior

    if Sentido == 'ns':
        X1 = 0
        Y1 = int(Altura * (100-Porcentaje) / 100)
        X2 = Anchura
        Y2 = Altura

    if Sentido == 'sn':
        X1 = 0
        Y1 = 0
        X2 = Anchura
        Y2 = int(Altura * Porcentaje / 100)

    if Sentido == 'oe':
        X1 = int(Anchura * (100-Porcentaje) / 100)
        Y1 = 0
        X2 = Anchura
        Y2 = Altura

    if Sentido == 'eo':
        X1 = 0
        Y1 = 0
        X2 = int(Anchura * Porcentaje / 100)
        Y2 = Altura

    # La orden para crear la máscara para componer las imágenes
    OrdenMascara = ('convert -size ' + str(Anchura) + 'x' + str(Altura) +
                    ' xc:white -fill black -draw "rectangle ' +
                    str(X1) + ',' + str(Y1) + ' ' + str(X2) + ',' + str(Y2) +
                    '" -blur 0x5 ' + Mascara)

    # La orden para componer las imágenes
    OrdenComponer = ('convert ' + Anterior + ' ' + Siguiente + ' ' + Mascara +
                     ' -composite ' + Nueva)

    # Decidimos qué orden devolver
    if ExisteMascara: OrdenFinal = OrdenComponer
    else: OrdenFinal = OrdenMascara + ';' + OrdenComponer

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def empuja_ns(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']

    # El archivo que contendrá el trozo de la imagen anterior
    TrozoAnt = DirAux + '/trozoant-' + Formato4(Paso) + '.png'

    # El archivo que contendrá el trozo de la imagen siguiente
    TrozoSig = DirAux + '/trozosig-' + Formato4(Paso) + '.png'

    # Calculamos la altura que debe tener la parte de la imagen anterior
    AltAnt = int(Altura * Porcentaje / 100)

    # Calculamos la altura que debe tener la parte de la imagen siguiente
    AltSig = Altura - AltAnt

    # La orden para crear el trozo de la imagen anterior
    OrdenAnt = ('convert ' + Anterior +
                ' -crop ' + str(Anchura) + 'x' + str(AltAnt) + '+0+0 ' +
                TrozoAnt)

    # La orden para crear el trozo de la imagen siguiente
    OrdenSig = ('convert ' + Siguiente +
                ' -crop ' + str(Anchura) + 'x' + str(AltSig) +
                '+0+' + str(Altura-AltSig) + ' ' +
                TrozoSig)

    # La orden para unir los dos trozos
    OrdenUnir = 'convert -append ' + TrozoSig + ' ' + TrozoAnt + ' ' + Nueva

    # Montamos la orden completa
    OrdenFinal = ';'.join([OrdenAnt, OrdenSig, OrdenUnir])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def empuja_sn(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']

    # El archivo que contendrá el trozo de la imagen anterior
    TrozoAnt = DirAux + '/trozoant-' + Formato4(Paso) + '.png'

    # El archivo que contendrá el trozo de la imagen siguiente
    TrozoSig = DirAux + '/trozosig-' + Formato4(Paso) + '.png'

    # Calculamos la altura que debe tener la parte de la imagen anterior
    AltAnt = int(Altura * Porcentaje / 100)

    # Calculamos la altura que debe tener la parte de la imagen siguiente
    AltSig = Altura - AltAnt

    # La orden para crear el trozo de la imagen anterior
    OrdenAnt = ('convert ' + Anterior +
                ' -crop ' + str(Anchura) + 'x' + str(AltAnt) +
                '+0+' + str(AltSig) + ' ' +
                TrozoAnt)

    # La orden para crear el trozo de la imagen siguiente
    OrdenSig = ('convert ' + Siguiente +
                ' -crop ' + str(Anchura) + 'x' + str(AltSig) + '+0+0 ' +
                TrozoSig)

    # La orden para unir los dos trozos
    OrdenUnir = 'convert -append ' + TrozoAnt + ' ' + TrozoSig + ' ' + Nueva

    # Montamos la orden completa
    OrdenFinal = ';'.join([OrdenAnt, OrdenSig, OrdenUnir])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def empuja_oe(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']

    # El archivo que contendrá el trozo de la imagen anterior
    TrozoAnt = DirAux + '/trozoant-' + Formato4(Paso) + '.png'

    # El archivo que contendrá el trozo de la imagen siguiente
    TrozoSig = DirAux + '/trozosig-' + Formato4(Paso) + '.png'

    # Calculamos la anchura que debe tener la parte de la imagen anterior
    AncAnt = int(Anchura * Porcentaje / 100)

    # Calculamos la anchura que debe tener la parte de la imagen siguiente
    AncSig = Anchura - AncAnt

    # La orden para crear el trozo de la imagen anterior
    OrdenAnt = ('convert ' + Anterior +
                ' -crop ' + str(AncAnt) + 'x' + str(Altura) + '+0+0 ' +
                TrozoAnt)

    # La orden para crear el trozo de la imagen siguiente
    OrdenSig = ('convert ' + Siguiente +
                ' -crop ' + str(AncSig) + 'x' + str(Altura) +
                '+' + str(Anchura-AncSig) + '+0 ' +
                TrozoSig)

    # La orden para unir los dos trozos
    OrdenUnir = 'convert +append ' + TrozoSig + ' ' + TrozoAnt + ' ' + Nueva

    # Montamos la orden completa
    OrdenFinal = ';'.join([OrdenAnt, OrdenSig, OrdenUnir])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def empuja_eo(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']

    # El archivo que contendrá el trozo de la imagen anterior
    TrozoAnt = DirAux + '/trozoant-' + Formato4(Paso) + '.png'

    # El archivo que contendrá el trozo de la imagen siguiente
    TrozoSig = DirAux + '/trozosig-' + Formato4(Paso) + '.png'

    # Calculamos la anchura que debe tener la parte de la imagen anterior
    AncAnt = int(Anchura * Porcentaje / 100)

    # Calculamos la anchura que debe tener la parte de la imagen siguiente
    AncSig = Anchura - AncAnt

    # La orden para crear el trozo de la imagen anterior
    OrdenAnt = ('convert ' + Anterior +
                ' -crop ' + str(AncAnt) + 'x' + str(Altura) +
                '+' + str(AncSig) + '+0 ' +
                TrozoAnt)

    # La orden para crear el trozo de la imagen siguiente
    OrdenSig = ('convert ' + Siguiente +
                ' -crop ' + str(AncSig) + 'x' + str(Altura) + '+0+0 ' +
                TrozoSig)

    # La orden para unir los dos trozos
    OrdenUnir = 'convert +append ' + TrozoAnt + ' ' + TrozoSig + ' ' + Nueva

    # Montamos la orden completa
    OrdenFinal = ';'.join([OrdenAnt, OrdenSig, OrdenUnir])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def rectangulo(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Sentido = Info['param1']

    # El archivo que contendrá la máscara
    Mascara = DirAux + '/mascara-' + Formato4(Paso) + '.png'

    # Vemos si ya ha sido creado anteriormente
    ExisteMascara = os.path.exists(Mascara)

    # Calculamos las dim. de la imagen interior y los colores de la máscara
    if Sentido == 'df':
        AncInt = int(Anchura * (100-Porcentaje) / 100)
        AltInt = int(Altura * (100-Porcentaje) / 100)
        ColorFondo = 'black'; ColorFrente = 'white'
    else:
        AncInt = int(Anchura * Porcentaje / 100)
        AltInt = int(Altura * Porcentaje / 100)
        ColorFondo = 'white'; ColorFrente = 'black'

    # Calculamos la posición de la imagen interior
    X1 = (Anchura-AncInt)/2
    Y1 = (Altura-AltInt)/2
    X2 = X1 + AncInt
    Y2 = Y1 + AltInt

    # La orden para crear la máscara para componer las imágenes
    OrdenMascara = ('convert -size ' + str(Anchura) + 'x' + str(Altura) +
                    ' xc:' + ColorFondo + ' -fill ' + ColorFrente +
                    ' -draw "rectangle ' +
                    str(X1) + ',' + str(Y1) + ' ' + str(X2) + ',' + str(Y2) +
                    '" -blur 0x5 ' + Mascara)

    # La orden para componer las imágenes
    OrdenComponer = ('convert ' + Anterior + ' ' + Siguiente + ' ' + Mascara +
                     ' -composite ' + Nueva)

    # Decidimos qué orden devolver
    if ExisteMascara: OrdenFinal = OrdenComponer
    else: OrdenFinal = OrdenMascara + ';' + OrdenComponer

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def aparece(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Parametro = Info['param1']

    # El archivo que contendrá la reducción de la imagen siguiente
    Reduccion = DirAux + '/reduccion-' + Formato4(Paso) + '.png'

    # Calculamos las dimensiones que debe tener la reducción
    # de la imagen siguiente
    AncSig = int(Anchura * (100-Porcentaje) / 100)
    AltSig = int(Altura * (100-Porcentaje) / 100)

    # La orden para reducir la imagen siguiente
    OrdenReducir = ('convert ' + Siguiente +
                    ' -resize ' + str(AncSig) + 'x' + str(AltSig) + '! ' +
                     Reduccion)

    # La orden para montar a la imagen anterior la reduccion de la siguiente
    if Parametro == 'c': Posicion = 'Center'
    if Parametro == 'no': Posicion = 'NorthWest'
    if Parametro == 'n': Posicion = 'North'
    if Parametro == 'ne': Posicion = 'NorthEast'
    if Parametro == 'o': Posicion = 'West'
    if Parametro == 'e': Posicion = 'East'
    if Parametro == 'so': Posicion = 'SouthWest'
    if Parametro == 's': Posicion = 'South'
    if Parametro == 'se': Posicion = 'SouthEast'
    OrdenMontar = ('composite -gravity ' + Posicion + ' ' + Reduccion +
                   ' ' + Anterior + ' ' + Nueva)

    # Montamos la orden completa
    OrdenFinal = OrdenReducir + ';' + OrdenMontar

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def desaparece(Info):
    # Tomamos las variables del argumento
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Porcentaje = Info['porcentaje']

    # La transición desaparece es igual que la transición aparece
    # intercambiando Anterior y Siguiente y el porcentaje
    Anterior, Siguiente = Siguiente, Anterior
    Porcentaje = 100 - Porcentaje

    # Guardamos los nuevos valores de las variables en el argumento
    Info['anterior'] = Anterior
    Info['siguiente'] = Siguiente
    Info['porcentaje'] = Porcentaje

    # Construimos el nombre de la función que calculará la orden
    Funcion = eval('aparece')

    # Devolvemos nosotros lo que diga la transición aparece
    return Funcion(Info)

#------------------------------------------------
def aleatorio(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Parametro = int(Info['param1'])

    # Si nos mandan un parámetro 0, usamos 1 (lo mínimo posible)
    if not Parametro: Parametro = 1

    # La lista con las celdas en que dividimos la imagen
    ListaCeldas = CreaListaCeldas(Parametro, Anchura, Altura)

    # El archivo que contendrá la máscara
    Mascara = DirAux + '/mascara-' + Formato4(Paso) + '.png'

    # El archivo que contendrá las órdenes de dibujo de cuadrados
    Dibujo = DirAux + '/dibujo' + Formato4(Paso) + '.mvg'
    Salida = open(Dibujo, 'w')
    Salida.write('viewbox 0 0 ' + str(Anchura) + ' ' + str(Altura) + '\n')
    Salida.write('fill #000 rectangle 0 0 ' +
                 str(Anchura) + ' ' + str(Altura) + '\n')

    # Decidimos aleatoriamente si cada celda se tomará de la imagen
    # anterior o de la siguiente
    Probabilidad = Porcentaje / 100
    for Fil, Col in ListaCeldas:
        if random.random() > Probabilidad:
            DibujaCuadrado(Salida, Fil, Col, Parametro)

    # Cerramos el fichero
    Salida.close()

    # La orden para crear la máscara para componer las imágenes
    OrdenMascara = 'convert mvg:' + Dibujo + ' ' + Mascara

    # La orden para componer las imágenes
    OrdenComponer = ('convert ' + Anterior + ' ' + Siguiente + ' ' + Mascara +
                     ' -composite ' + Nueva)

    # Devolvemos la orden
    return OrdenMascara + ';' + OrdenComponer

#------------------------------------------------
def mosaico(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Total = Info['total']
    Parametro = int(Info['param1'])

    # Si nos mandan un parámetro 0, usamos 1 (lo mínimo posible)
    if not Parametro: Parametro = 1

    # Averiguamos si debemos distinguir entre dos lados o no
    if Info['param2']: Modo = Info['param2']
    else: Modo = 'todo'

    # El archivo que contendrá la máscara
    # Hay que escribir sobre él en cada paso
    Mascara = DirAux + '/mascara-' + Formato4(Paso) + '.png'

    # El archivo que contendrá las órdenes de dibujo de cuadrados
    Dibujo = DirAux + '/dibujo' + Formato4(Paso) + '.mvg'

    # Si es la primera vez que nos llaman, hay que crearlo todo
    if Paso == 0:

        # El diccionario que guardará los datos entre dos llamadas
        mosaico.Datos[Modo] = {}

        # La lista con las celdas en que dividimos la imagen
        ListaCeldas = CreaListaCeldas(Parametro, Anchura, Altura)

        # La revolvemos aleatoriamente
        random.shuffle(ListaCeldas)

        # Nos la guardamos en el objeto función
        mosaico.Datos[Modo]['ListaCeldas'] = ListaCeldas

        # Vemos cuántas celdas hay que usar en cada paso
        # Lo usamos float para repartirlas más uniformemente
        CeldasCadaPaso = float(len(ListaCeldas)) / (Total+1)

        # Nos lo guardamos en el objeto función
        mosaico.Datos[Modo]['CeldasCadaPaso'] = CeldasCadaPaso

        # Ponemos a cero los contadores de celdas usadas
        mosaico.Datos[Modo]['CeldasUsadasInt'] = 0
        mosaico.Datos[Modo]['CeldasUsadasFloat'] = 0.0

    # En cualquier paso, tenemos que seleccionar qué celdas vamos a modificar

    # Tomamos del objeto función los datos que calculamos en el paso 0
    CeldasCadaPaso = mosaico.Datos[Modo]['CeldasCadaPaso']
    ListaCeldas = mosaico.Datos[Modo]['ListaCeldas']
    CeldasUsadasInt = mosaico.Datos[Modo]['CeldasUsadasInt']
    CeldasUsadasFloat = mosaico.Datos[Modo]['CeldasUsadasFloat']

    # Vemos cuántas celdas vamos a usar en este paso
    CeldasUsadasFloat += CeldasCadaPaso
    NumCeldasEstePaso = int(CeldasUsadasFloat - CeldasUsadasInt)
    CeldasUsadasInt += NumCeldasEstePaso

    # Obtenemos la lista de celdas que vamos a usar
    ListaCeldasEstePaso = ListaCeldas[:CeldasUsadasInt]

    # Creamos la orden de dibujo
    Salida = open(Dibujo, 'w')
    Salida.write('viewbox 0 0 ' + str(Anchura) + ' ' + str(Altura) + '\n')
    Salida.write('fill #000 rectangle 0 0 ' +
                 str(Anchura) + ' ' + str(Altura) + '\n')

    # Dibujamos los cuadrados de las celdas de este paso
    for Fil, Col in ListaCeldasEstePaso:
        DibujaCuadrado(Salida, Fil, Col, Parametro)

    # Cerramos el fichero
    Salida.close()

    # Guardamos en el objeto función los nuevos valores
    mosaico.Datos[Modo]['CeldasUsadasFloat'] = CeldasUsadasFloat
    mosaico.Datos[Modo]['CeldasUsadasInt'] = CeldasUsadasInt

    # La orden para crear la máscara para componer las imágenes
    OrdenMascara = 'convert mvg:' + Dibujo + ' ' + Mascara

    # La orden para componer las imágenes
    OrdenComponer = ('convert ' + Anterior + ' ' + Siguiente + ' ' + Mascara +
                     ' -composite ' + Nueva)

    # Devolvemos la orden
    return OrdenMascara + ';' + OrdenComponer

#------------------------------------------------
def cascada(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Total = Info['total']
    Parametro = Info['param1']
    Tamano = int(Info['param2'])

    # Si nos mandan un tamaño 0, usamos 1 (lo mínimo posible)
    if not Tamano: Tamano = 1

    # Averiguamos si debemos distinguir entre dos lados o no
    if Info['param3']: Modo = Info['param3']
    else: Modo = 'todo'

    # El archivo que contendrá la máscara
    Mascara = DirAux + '/mascara-' + Formato4(Paso) + '.png'

    # El archivo que contendrá las órdenes de dibujo de cuadrados
    Dibujo = DirAux + '/dibujo' + Formato4(Paso) + '.mvg'

    # Si es la primera vez que nos llaman, hay que crearlo todo
    if Paso == 0:

        # El diccionario que guardará los datos entre dos llamadas
        cascada.Datos[Modo] = {}

        # La lista con las celdas en que dividimos la imagen
        ListaCeldas = CreaListaCeldas(Tamano, Anchura, Altura)
        cascada.Datos[Modo]['ListaCeldas'] = ListaCeldas

        # Vemos cuántas celdas hay que usar en cada paso
        # Lo usamos float para repartirlas más uniformemente
        CeldasCadaPaso = float(len(ListaCeldas)) / Total
        cascada.Datos[Modo]['CeldasCadaPaso'] = CeldasCadaPaso

        # Ordenamos las celdas según el criterio del parámetro
        if Parametro == 'ns': Funcion = lambda celda: celda[0]
        if Parametro == 'sn': Funcion = lambda celda: -celda[0]
        if Parametro == 'oe': Funcion = lambda celda: celda[1]
        if Parametro == 'eo': Funcion = lambda celda: -celda[1]
        if Parametro == 'nose': Funcion = lambda c: c[0]+c[1]
        if Parametro == 'seno': Funcion = lambda c: -c[0]-c[1]
        if Parametro == 'neso': Funcion = lambda c: c[0]-c[1]
        if Parametro == 'sone': Funcion = lambda c: -c[0]+c[1]
        ListaCeldas.sort(key=Funcion)

        # Ponemos a cero la lista y los contadores de celdas usadas y retrasos
        cascada.Datos[Modo]['ListaCeldasUsadas'] = []
        cascada.Datos[Modo]['CeldasUsadasInt'] = 0
        cascada.Datos[Modo]['CeldasUsadasFloat'] = 0.0
        cascada.Datos[Modo]['Retraso'] = 0

    # En cualquier paso, tenemos que seleccionar qué celdas vamos a modificar

    # Tomamos del objeto función los datos que calculamos en el paso 0
    CeldasCadaPaso = cascada.Datos[Modo]['CeldasCadaPaso']
    ListaCeldas = cascada.Datos[Modo]['ListaCeldas']
    ListaCeldasUsadas = cascada.Datos[Modo]['ListaCeldasUsadas']
    CeldasUsadasInt = cascada.Datos[Modo]['CeldasUsadasInt']
    CeldasUsadasFloat = cascada.Datos[Modo]['CeldasUsadasFloat']
    Retraso = cascada.Datos[Modo]['Retraso']

    # Vemos cuántas celdas vamos a usar en este paso
    CeldasUsadasFloat += CeldasCadaPaso
    NumCeldasEstePaso = int(CeldasUsadasFloat - CeldasUsadasInt) + Retraso
    CeldasUsadasInt += NumCeldasEstePaso

    # Obtenemos la lista de celdas que vamos a trabajar en este paso
    ListaCeldasEstePaso = ListaCeldas[:NumCeldasEstePaso]

    # Las eliminamos de la lista general de celdas
    ListaCeldas = ListaCeldas[NumCeldasEstePaso:]

    # Creamos la orden de dibujo
    Salida = open(Dibujo, 'w')
    Salida.write('viewbox 0 0 ' + str(Anchura) + ' ' + str(Altura) + '\n')
    Salida.write('fill #000 rectangle 0 0 ' +
                 str(Anchura) + ' ' + str(Altura) + '\n')

    # Dibujamos los cuadrados de las celdas que ya llevamos
    # Esto incluye las que en el paso anterior no dibujamos ni retrasamos
    for Fil, Col in ListaCeldasUsadas:
        DibujaCuadrado(Salida, Fil, Col, Tamano)

    # En este paso realmente solo cambiamos algunas de las celdas
    ListaRetraso = []
    for i, Celda in enumerate(ListaCeldasEstePaso):
        # La probabilidad de cambiar una celda es alta al principio
        Probabilidad = 1 - float(i+1) / NumCeldasEstePaso
        # Si hay suerte, esta celda se cambia
        if random.random() < Probabilidad:
            Fil, Col = Celda
            DibujaCuadrado(Salida, Fil, Col, Tamano)
        # Si no, esta celda puede ser elegida para ser retrasada
        elif random.random() < 0.1: ListaRetraso.append(Celda)

    # Cerramos el fichero
    Salida.close()

    # Movemos de sitio las celdas elegidas como retrasadas
    for Celda in ListaRetraso:
        ListaCeldasEstePaso.remove(Celda)
        Minimo =  min(int(len(ListaCeldas)/2),2*int(CeldasCadaPaso))
        x = random.randrange(Minimo)
        ListaCeldas.insert(x, Celda)

    # Guardamos en el objeto función los nuevos valores
    cascada.Datos[Modo]['CeldasUsadasFloat'] = CeldasUsadasFloat
    cascada.Datos[Modo]['CeldasUsadasInt'] += NumCeldasEstePaso
    cascada.Datos[Modo]['ListaCeldasUsadas'] += ListaCeldasEstePaso
    cascada.Datos[Modo]['ListaCeldas'] = ListaCeldas
    cascada.Datos[Modo]['Retraso'] = len(ListaRetraso)

    # La orden para crear la máscara para componer las imágenes
    OrdenMascara = 'convert mvg:' + Dibujo + ' ' + Mascara

    # La orden para componer las imágenes
    OrdenComponer = ('convert ' + Anterior + ' ' + Siguiente + ' ' + Mascara +
                     ' -composite ' + Nueva)

    # Devolvemos la orden
    return OrdenMascara + ';' + OrdenComponer

#------------------------------------------------
def mancha(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Total = Info['total']
    Parametro = Info['param1']
    Tamano = int(Info['param2'])

    # Si nos mandan un tamaño 0, usamos 1 (lo mínimo posible)
    if not Tamano: Tamano = 1

    # Averiguamos si debemos distinguir entre dos lados o no
    if Info['param3']: Modo = Info['param3']
    else: Modo = 'todo'

    # El archivo que contendrá la máscara
    Mascara = DirAux + '/mascara-' + Formato4(Paso) + '.png'

    # El archivo que contendrá las órdenes de dibujo de cuadrados
    Dibujo = DirAux + '/dibujo' + Formato4(Paso) + '.mvg'

    # Si es la primera vez que nos llaman, hay que crearlo todo
    if Paso == 0:

        # El diccionario que guardará los datos entre dos llamadas
        mancha.Datos[Modo] = {}

        # De cuántas filas y columnas de celdas disponemos
        MaxFil = Altura / Tamano
        MaxCol = Anchura / Tamano

        # La lista con las celdas en que dividimos la imagen
        ListaCeldas = CreaListaCeldas(Tamano, Anchura, Altura)

        # Si una celda está diponible para ser candidata o no
        CeldaDisponible = {}
        # Al principio todas están disponibles
        for Celda in ListaCeldas: CeldaDisponible[Celda] = True

        # El diccionario de vecinas de cada celda
        Vecinas = {}
        # Cada elemento del diccionario es una lista con las vecinas
        for Celda in ListaCeldas: Vecinas[Celda] = []
        # Las cuatro esquinas tienen solo dos vecinas
        Vecinas[(0,0)].append((0,1))
        Vecinas[(0,0)].append((1,0))
        Vecinas[(0,MaxCol-1)].append((0,MaxCol-2))
        Vecinas[(0,MaxCol-1)].append((1,MaxCol-1))
        Vecinas[(MaxFil-1,0)].append((MaxFil-1,1))
        Vecinas[(MaxFil-1,0)].append((MaxFil-2,0))
        Vecinas[(MaxFil-1,MaxCol-1)].append((MaxFil-2,MaxCol-1))
        Vecinas[(MaxFil-1,MaxCol-1)].append((MaxFil-1,MaxCol-2))
        # Los bordes tienen tres vecinas
        for i in range(1, MaxCol-1):
            Vecinas[(0,i)].append((0,i-1))
            Vecinas[(0,i)].append((0,i+1))
            Vecinas[(0,i)].append((1,i))
            Vecinas[(MaxFil-1,i)].append((MaxFil-1,i-1))
            Vecinas[(MaxFil-1,i)].append((MaxFil-1,i+1))
            Vecinas[(MaxFil-1,i)].append((MaxFil-2,i))
        for i in range(1, MaxFil-1):
            Vecinas[(i,0)].append((i-1,0))
            Vecinas[(i,0)].append((i+1,0))
            Vecinas[(i,0)].append((i,1))
            Vecinas[(i,MaxCol-1)].append((i-1,MaxCol-1))
            Vecinas[(i,MaxCol-1)].append((i+1,MaxCol-1))
            Vecinas[(i,MaxCol-1)].append((i,MaxCol-2))
        # Las demás celdas tienen cuatro vecinas
        for i in range(1, MaxFil-1):
            for j in range(1, MaxCol-1):
                Vecinas[(i,j)].append((i-1,j))
                Vecinas[(i,j)].append((i+1,j))
                Vecinas[(i,j)].append((i,j-1))
                Vecinas[(i,j)].append((i,j+1))

        # Vemos cuántas celdas hay que usar en cada paso
        # Lo usamos float para repartirlas más uniformemente
        CeldasCadaPaso = float(len(ListaCeldas)) / (Total+1)
        mancha.Datos[Modo]['CeldasCadaPaso'] = CeldasCadaPaso

        # Elegimos la celda inicial
        if Parametro == 'a':
            # Elegimos aleatoriamente una celda que no esté cerca ni de
            # los extremos ni del centro
            if MaxFil > 14:
                Septimo = MaxFil / 7
                if random.random() < 0.5:
                    MenorFil = 1 * Septimo
                    MayorFil = 3 * Septimo
                else:
                    MenorFil = 4 * Septimo
                    MayorFil = 6 * Septimo
            else:
                MenorFil = 2
                MayorFil = MaxFil-2
            if MaxCol > 14:
                Septimo = MaxCol / 7
                if random.random() < 0.5:
                    MenorCol = 1 * Septimo
                    MayorCol = 3 * Septimo
                else:
                    MenorCol = 4 * Septimo
                    MayorCol = 6 * Septimo
            else:
                MenorCol = 2
                MayorCol = MaxCol-2
            Fil = random.randrange(MenorFil, MayorFil)
            Col = random.randrange(MenorCol, MayorCol)
        elif Parametro == 'c':  Fil = MaxFil / 2 ; Col = MaxCol / 2
        elif Parametro == 'n':  Fil = 0          ; Col = MaxCol / 2
        elif Parametro == 's':  Fil = MaxFil - 1 ; Col = MaxCol / 2
        elif Parametro == 'e':  Fil = MaxFil / 2 ; Col = MaxCol - 1
        elif Parametro == 'o':  Fil = MaxFil / 2 ; Col = 0
        elif Parametro == 'no': Fil = 0          ; Col = 0
        elif Parametro == 'ne': Fil = 0          ; Col = MaxCol - 1
        elif Parametro == 'so': Fil = MaxFil - 1 ; Col = 0
        elif Parametro == 'se': Fil = MaxFil - 1 ; Col = MaxCol - 1

        # Creamos las listas de celdas usadas y candidatas
        ListaCeldasUsadas = []
        ListaCeldasCandidatas = []

        # Usamos la celda inicial
        CeldaIni = (Fil, Col)
        CeldaDisponible[CeldaIni] = False
        ListaCeldasUsadas.append(CeldaIni)

        # Añadimos a la lista de candidatas todas sus vecinas
        for Celda in Vecinas[CeldaIni]:
            ListaCeldasCandidatas.append(Celda)
            CeldaDisponible[Celda] = False

        # Guardamos en el objeto función los datos que hay que conservar
        mancha.Datos[Modo]['CeldasUsadasInt'] = 1
        mancha.Datos[Modo]['CeldasUsadasFloat'] = 1.0
        mancha.Datos[Modo]['Vecinas'] = Vecinas
        mancha.Datos[Modo]['ListaCeldasUsadas'] = ListaCeldasUsadas
        mancha.Datos[Modo]['ListaCeldasCandidatas'] = ListaCeldasCandidatas
        mancha.Datos[Modo]['CeldaDisponible'] = CeldaDisponible

    # En cualquier paso, tenemos que añadir unas cuantas celdas

    # Tomamos del objeto función los datos que calculamos en el paso 0
    Vecinas = mancha.Datos[Modo]['Vecinas']
    CeldasCadaPaso = mancha.Datos[Modo]['CeldasCadaPaso']
    ListaCeldasUsadas = mancha.Datos[Modo]['ListaCeldasUsadas']
    ListaCeldasCandidatas = mancha.Datos[Modo]['ListaCeldasCandidatas']
    CeldasUsadasInt = mancha.Datos[Modo]['CeldasUsadasInt']
    CeldasUsadasFloat = mancha.Datos[Modo]['CeldasUsadasFloat']
    CeldaDisponible = mancha.Datos[Modo]['CeldaDisponible']

    # Vemos cuántas celdas vamos a añadir en este paso
    CeldasUsadasFloat += CeldasCadaPaso
    NumCeldasEstePaso = int(CeldasUsadasFloat - CeldasUsadasInt)

    # Añadimos el número indicado de celdas
    for i in range(NumCeldasEstePaso):
        # Elegimos una de las candidatas
        CeldaElegida = random.choice(ListaCeldasCandidatas)
        # La eliminamos de la lista de candidatas
        ListaCeldasCandidatas.remove(CeldaElegida)
        # La añadimos a la lista de usadas
        ListaCeldasUsadas.append(CeldaElegida)
        CeldaDisponible[CeldaElegida] = False
        # Añadimos a la lista de candidatas las vecinas disponibles
        for Celda in Vecinas[CeldaElegida]:
            if CeldaDisponible[Celda]:
                ListaCeldasCandidatas.append(Celda)
                CeldaDisponible[Celda] = False

    # Creamos la orden de dibujo
    Salida = open(Dibujo, 'w')
    Salida.write('viewbox 0 0 ' + str(Anchura) + ' ' + str(Altura) + '\n')
    Salida.write('fill #000 rectangle 0 0 ' +
                 str(Anchura) + ' ' + str(Altura) + '\n')

    # Dibujamos los cuadrados de las celdas que ya llevamos usadas
    for Fil, Col in ListaCeldasUsadas:
        DibujaCuadrado(Salida, Fil, Col, Tamano)

    # Cerramos el fichero
    Salida.close()

    # Guardamos en el objeto función los nuevos valores
    mancha.Datos[Modo]['CeldasUsadasFloat'] = CeldasUsadasFloat
    mancha.Datos[Modo]['CeldasUsadasInt'] += NumCeldasEstePaso
    mancha.Datos[Modo]['ListaCeldasUsadas'] = ListaCeldasUsadas
    mancha.Datos[Modo]['ListaCeldasCandidatas'] = ListaCeldasCandidatas
    mancha.Datos[Modo]['CeldaDisponible'] = CeldaDisponible

    # La orden para crear la máscara para componer las imágenes
    OrdenMascara = 'convert mvg:' + Dibujo + ' -blur 0x2 ' + Mascara

    # La orden para componer las imágenes
    OrdenComponer = ('convert ' + Anterior + ' ' + Siguiente + ' ' + Mascara +
                     ' -composite ' + Nueva)

    # Devolvemos la orden
    return OrdenMascara + ';' + OrdenComponer

#------------------------------------------------
def cuadrados(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Sentido = Info['param1']
    Tamano = int(Info['param2'])

    # Si nos mandan un tamaño 0 es que tenemos que calcular el MCD
    if not Tamano: Tamano = mcd(Anchura, Altura)

    # La lista con las celdas en que dividimos la imagen
    ListaCeldas = CreaListaCeldas(Tamano, Anchura, Altura)

    # El archivo que contendrá la máscara
    Mascara = DirAux + '/mascara-' + Formato4(Paso) + '.png'

    # Vemos si ya ha sido creado anteriormente
    ExisteMascara = os.path.exists(Mascara)

    # Calculamos la dimensión que debe tener cada cuadrado
    # y los colores de la máscara
    if Sentido == 'df':
        Lado = int(Tamano * (100-Porcentaje) / 100)
        ColorFondo = '000'; ColorFrente = 'FFF'
    else:
        Lado = int(Tamano * Porcentaje / 100)
        ColorFondo = 'FFF'; ColorFrente = '000'

    # El archivo que contendrá las órdenes de dibujo de cuadrados
    Dibujo = DirAux + '/dibujo' + Formato4(Paso) + '.mvg'
    Salida = open(Dibujo, 'w')
    Salida.write('viewbox 0 0 ' + str(Anchura) + ' ' + str(Altura) + '\n')
    Salida.write('fill #' + ColorFondo + ' rectangle 0 0 ' +
                 str(Anchura) + ' ' + str(Altura) + '\n')
    for Fil, Col in ListaCeldas:
        Cadena = 'fill #' + ColorFrente + ' rectangle '
        Cadena += str(Col * Tamano + (Tamano-Lado)/2) + ' '
        Cadena += str(Fil * Tamano + (Tamano-Lado)/2) + ' '
        Cadena += str(Col * Tamano + (Tamano+Lado)/2) + ' '
        Cadena += str(Fil * Tamano + (Tamano+Lado)/2)
        Salida.write(Cadena + '\n')
    # Cerramos el fichero
    Salida.close()

    # La orden para crear la máscara para componer las imágenes
    OrdenMascara = 'convert mvg:' + Dibujo + ' -blur 0x2 ' + Mascara

    # La orden para componer las imágenes
    OrdenComponer = ('convert ' + Anterior + ' ' + Siguiente + ' ' + Mascara +
                     ' -composite ' + Nueva)

    # Decidimos qué orden devolver
    if ExisteMascara: OrdenFinal = OrdenComponer
    else: OrdenFinal = OrdenMascara + ';' + OrdenComponer

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def circulos(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Sentido = Info['param1']
    Tamano = int(Info['param2'])

    # Si nos mandan un tamaño 0 es que tenemos que calcular el MCD
    if not Tamano: Tamano = mcd(Anchura, Altura)

    # La lista con las celdas en que dividimos la imagen
    ListaCeldas = CreaListaCeldas(Tamano, Anchura, Altura)

    # El archivo que contendrá la máscara
    Mascara = DirAux + '/mascara-' + Formato4(Paso) + '.png'

    # Vemos si ya ha sido creado anteriormente
    ExisteMascara = os.path.exists(Mascara)

    # Calculamos el radio que debe tener cada círculo
    # y los colores de la máscara
    if Sentido == 'df':
        Radio = int(Tamano * 0.5 * (100-Porcentaje) / 100)
        ColorFondo = '000'; ColorFrente = 'FFF'
    else:
        Radio = int(Tamano * 0.5 * Porcentaje / 100)
        ColorFondo = 'FFF'; ColorFrente = '000'

    # El archivo que contendrá las órdenes de dibujo de círculos
    Dibujo = DirAux + '/dibujo' + Formato4(Paso) + '.mvg'
    Salida = open(Dibujo, 'w')
    Salida.write('viewbox 0 0 ' + str(Anchura) + ' ' + str(Altura) + '\n')
    Salida.write('fill #' + ColorFondo + ' rectangle 0 0 ' +
                 str(Anchura) + ' ' + str(Altura) + '\n')
    for Fil, Col in ListaCeldas:
        Cadena = 'fill #' + ColorFrente + ' circle '
        Cadena += str(int((Col+0.5) * Tamano)) + ' '
        Cadena += str(int((Fil+0.5) * Tamano)) + ' '
        Cadena += str(int((Col+0.5) * Tamano) + Radio) + ' '
        Cadena += str(int((Fil+0.5) * Tamano) + Radio)
        Salida.write(Cadena + '\n')
    # Cerramos el fichero
    Salida.close()

    # La orden para crear la máscara para componer las imágenes
    OrdenMascara = 'convert mvg:' + Dibujo + ' -blur 0x2 ' + Mascara

    # La orden para componer las imágenes
    OrdenComponer = ('convert ' + Anterior + ' ' + Siguiente + ' ' + Mascara +
                     ' -composite ' + Nueva)

    # Decidimos qué orden devolver
    if ExisteMascara: OrdenFinal = OrdenComponer
    else: OrdenFinal = OrdenMascara + ';' + OrdenComponer

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def quitar_s(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']

    # El archivo que contendrá el trozo de la imagen anterior
    TrozoAnt = DirAux + '/trozo-' + Formato4(Paso) + '.png'

    # Calculamos la altura que debe tener la parte de la imagen anterior
    AltAnt = int(Altura * Porcentaje / 100)

    # La orden para crear el trozo de la imagen anterior
    OrdenAnt = ('convert ' + Anterior +
                ' -crop ' + str(Anchura) + 'x' + str(AltAnt) + '+0+0 ' +
                TrozoAnt)

    # La orden para unir los dos trozos
    OrdenUnir = ('composite -gravity South ' + TrozoAnt + ' ' + Siguiente + ' '
                 + Nueva)

    # Montamos la orden completa
    OrdenFinal = ';'.join([OrdenAnt, OrdenUnir])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def quitar_n(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']

    # El archivo que contendrá el trozo de la imagen anterior
    TrozoAnt = DirAux + '/trozo-' + Formato4(Paso) + '.png'

    # Calculamos la altura que debe tener la parte de la imagen anterior
    AltAnt = int(Altura * Porcentaje / 100)

    # La orden para crear el trozo de la imagen anterior
    OrdenAnt = ('convert ' + Anterior +
                ' -crop ' + str(Anchura) + 'x' + str(AltAnt) +
                '+0+' + str(Altura-AltAnt) + ' ' +
                TrozoAnt)

    # La orden para unir los dos trozos
    OrdenUnir = ('composite -gravity North ' + TrozoAnt + ' ' + Siguiente + ' '
                 + Nueva)

    # Montamos la orden completa
    OrdenFinal = ';'.join([OrdenAnt, OrdenUnir])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def quitar_e(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']

    # El archivo que contendrá el trozo de la imagen anterior
    TrozoAnt = DirAux + '/trozo-' + Formato4(Paso) + '.png'

    # Calculamos la anchura que debe tener la parte de la imagen anterior
    AncAnt = int(Anchura * Porcentaje / 100)

    # La orden para crear el trozo de la imagen anterior
    OrdenAnt = ('convert ' + Anterior +
                ' -crop ' + str(AncAnt) + 'x' + str(Altura) + '+0+0 ' +
                TrozoAnt)

    # La orden para unir los dos trozos
    OrdenUnir = ('composite -gravity East ' + TrozoAnt + ' ' + Siguiente + ' '
                 + Nueva)

    # Montamos la orden completa
    OrdenFinal = ';'.join([OrdenAnt, OrdenUnir])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def quitar_o(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']

    # El archivo que contendrá el trozo de la imagen anterior
    TrozoAnt = DirAux + '/trozo-' + Formato4(Paso) + '.png'

    # Calculamos la anchura que debe tener la parte de la imagen anterior
    AncAnt = int(Anchura * Porcentaje / 100)

    # La orden para crear el trozo de la imagen anterior
    OrdenAnt = ('convert ' + Anterior +
                ' -crop ' + str(AncAnt) + 'x' + str(Altura) +
                '+' + str(Anchura-AncAnt) + '+0 ' +
                TrozoAnt)

    # La orden para unir los dos trozos
    OrdenUnir = ('composite -gravity West ' + TrozoAnt + ' ' + Siguiente + ' '
                 + Nueva)

    # Montamos la orden completa
    OrdenFinal = ';'.join([OrdenAnt, OrdenUnir])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def quitar_se(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']

    # El archivo que contendrá el trozo de la imagen anterior
    TrozoAnt = DirAux + '/trozo-' + Formato4(Paso) + '.png'

    # Calculamos la altura que debe tener la parte de la imagen anterior
    AltAnt = int(Altura * Porcentaje / 100)

    # Calculamos la anchura que debe tener la parte de la imagen anterior
    AncAnt = int(Anchura * Porcentaje / 100)

    # La orden para crear el trozo de la imagen anterior
    OrdenAnt = ('convert ' + Anterior +
                ' -crop ' + str(AncAnt) + 'x' + str(AltAnt) + '+0+0 ' +
                TrozoAnt)

    # La orden para unir los dos trozos
    OrdenUnir = ('composite -gravity SouthEast ' + TrozoAnt + ' ' +
                 Siguiente + ' ' + Nueva)

    # Montamos la orden completa
    OrdenFinal = ';'.join([OrdenAnt, OrdenUnir])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def quitar_ne(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']

    # El archivo que contendrá el trozo de la imagen anterior
    TrozoAnt = DirAux + '/trozo-' + Formato4(Paso) + '.png'

    # Calculamos la altura que debe tener la parte de la imagen anterior
    AltAnt = int(Altura * Porcentaje / 100)

    # Calculamos la anchura que debe tener la parte de la imagen anterior
    AncAnt = int(Anchura * Porcentaje / 100)

    # La orden para crear el trozo de la imagen anterior
    OrdenAnt = ('convert ' + Anterior +
                ' -crop ' + str(AncAnt) + 'x' + str(AltAnt) +
                '+0+' + str(Altura-AltAnt) + ' ' +
                TrozoAnt)

    # La orden para unir los dos trozos
    OrdenUnir = ('composite -gravity NorthEast ' + TrozoAnt + ' ' +
                 Siguiente + ' ' + Nueva)

    # Montamos la orden completa
    OrdenFinal = ';'.join([OrdenAnt, OrdenUnir])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def quitar_so(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']

    # El archivo que contendrá el trozo de la imagen anterior
    TrozoAnt = DirAux + '/trozo-' + Formato4(Paso) + '.png'

    # Calculamos la altura que debe tener la parte de la imagen anterior
    AltAnt = int(Altura * Porcentaje / 100)

    # Calculamos la anchura que debe tener la parte de la imagen anterior
    AncAnt = int(Anchura * Porcentaje / 100)

    # La orden para crear el trozo de la imagen anterior
    OrdenAnt = ('convert ' + Anterior +
                ' -crop ' + str(AncAnt) + 'x' + str(AltAnt) +
                '+' + str(Anchura-AncAnt) + '+0 ' +
                TrozoAnt)

    # La orden para unir los dos trozos
    OrdenUnir = ('composite -gravity SouthWest ' + TrozoAnt + ' ' +
                 Siguiente + ' ' + Nueva)

    # Montamos la orden completa
    OrdenFinal = ';'.join([OrdenAnt, OrdenUnir])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def quitar_no(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']

    # El archivo que contendrá el trozo de la imagen anterior
    TrozoAnt = DirAux + '/trozo-' + Formato4(Paso) + '.png'

    # Calculamos la altura que debe tener la parte de la imagen anterior
    AltAnt = int(Altura * Porcentaje / 100)

    # Calculamos la anchura que debe tener la parte de la imagen anterior
    AncAnt = int(Anchura * Porcentaje / 100)

    # La orden para crear el trozo de la imagen anterior
    OrdenAnt = ('convert ' + Anterior +
                ' -crop ' + str(AncAnt) + 'x' + str(AltAnt) +
                '+' + str(Anchura-AncAnt) + '+' + str(Altura-AltAnt) + ' ' +
                TrozoAnt)

    # La orden para unir los dos trozos
    OrdenUnir = ('composite -gravity NorthWest ' + TrozoAnt + ' ' +
                 Siguiente + ' ' + Nueva)

    # Montamos la orden completa
    OrdenFinal = ';'.join([OrdenAnt, OrdenUnir])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def quitar_ns(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']

    # El archivo que contendrá el trozo superior de la imagen anterior
    TrozoSupAnt = DirAux + '/trozosup-' + Formato4(Paso) + '.png'

    # El archivo que contendrá el trozo inferior de la imagen anterior
    TrozoInfAnt = DirAux + '/trozoinf-' + Formato4(Paso) + '.png'

    # El archivo que contendrá la unión intermedia
    Intermedio = DirAux + '/intermedio-' + Formato4(Paso) + '.png'

    # Calculamos la altura que debe tener cada parte de la imagen anterior
    AltAnt = int(Altura * Porcentaje / 200)

    # La orden para crear el trozo superior de la imagen anterior
    OrdenSupAnt = ('convert ' + Anterior +
                   ' -crop ' + str(Anchura) + 'x' + str(AltAnt) +
                   '+0+' + str(int(Altura/2-AltAnt)) + ' ' +
                   TrozoSupAnt)

    # La orden para crear el trozo inferior de la imagen anterior
    OrdenInfAnt = ('convert ' + Anterior +
                   ' -crop ' + str(Anchura) + 'x' + str(AltAnt) +
                   '+0+' + str(int(Altura/2)) + ' ' +
                   TrozoInfAnt)

    # La orden para unir el trozo superior
    OrdenUnirSup = ('composite -gravity North ' + TrozoSupAnt + ' ' +
                    Siguiente + ' ' + Intermedio)

    # La orden para unir el trozo inferior
    OrdenUnirInf = ('composite -gravity South ' + TrozoInfAnt + ' ' +
                    Intermedio + ' ' + Nueva)

    # Montamos la orden completa
    OrdenFinal = ';'.join([OrdenSupAnt, OrdenInfAnt, OrdenUnirSup,
                           OrdenUnirInf])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def quitar_sn(Info): return quitar_ns(Info)

#------------------------------------------------
def quitar_eo(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']

    # El archivo que contendrá el trozo derecho de la imagen anterior
    TrozoDerAnt = DirAux + '/trozoder-' + Formato4(Paso) + '.png'

    # El archivo que contendrá el trozo izquierdo de la imagen anterior
    TrozoIzqAnt = DirAux + '/trozoizq-' + Formato4(Paso) + '.png'

    # El archivo que contendrá la unión intermedia
    Intermedio = DirAux + '/intermedio-' + Formato4(Paso) + '.png'

    # Calculamos la anchura que debe tener cada parte de la imagen anterior
    AncAnt = int(Anchura * Porcentaje / 200)

    # La orden para crear el trozo izquierdo de la imagen anterior
    OrdenIzqAnt = ('convert ' + Anterior +
                   ' -crop ' + str(AncAnt) + 'x' + str(Altura) +
                   '+' + str(int(Anchura/2-AncAnt)) + '+0 ' +
                   TrozoIzqAnt)

    # La orden para crear el trozo derecho de la imagen anterior
    OrdenDerAnt = ('convert ' + Anterior +
                   ' -crop ' + str(AncAnt) + 'x' + str(Altura) +
                   '+' + str(int(Anchura/2)) + '+0 ' +
                   TrozoDerAnt)

    # La orden para unir el trozo derecho
    OrdenUnirDer = ('composite -gravity East ' + TrozoDerAnt + ' ' +
                    Siguiente + ' ' + Intermedio)

    # La orden para unir el trozo izquierdo
    OrdenUnirIzq = ('composite -gravity West ' + TrozoIzqAnt + ' ' +
                    Intermedio + ' ' + Nueva)

    # Montamos la orden completa
    OrdenFinal = ';'.join([OrdenDerAnt, OrdenIzqAnt, OrdenUnirDer,
                           OrdenUnirIzq])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def quitar_oe(Info): return quitar_eo(Info)

#------------------------------------------------
def quitar_v(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']

    # El archivo que contendrá el trozo superior derecho de la imagen anterior
    TrozoSupDerAnt = DirAux + '/trozosupder-' + Formato4(Paso) + '.png'

    # El archivo que contendrá el trozo inferior derecho de la imagen anterior
    TrozoInfDerAnt = DirAux + '/trozoinfder-' + Formato4(Paso) + '.png'

    # El archivo que contendrá el trozo superior izquierdo de la imagen anterior
    TrozoSupIzqAnt = DirAux + '/trozosupizq-' + Formato4(Paso) + '.png'

    # El archivo que contendrá el trozo inferior izquierdo de la imagen anterior
    TrozoInfIzqAnt = DirAux + '/trozoinfizq-' + Formato4(Paso) + '.png'

    # Los archivos que contendrán las uniones intermedias
    Intermedio1 = DirAux + '/intermedio1-' + Formato4(Paso) + '.png'
    Intermedio2 = DirAux + '/intermedio2-' + Formato4(Paso) + '.png'
    Intermedio3 = DirAux + '/intermedio3-' + Formato4(Paso) + '.png'

    # Calculamos la altura que debe tener cada parte de la imagen anterior
    AltAnt = int(Altura * Porcentaje / 200)

    # Calculamos la anchura que debe tener cada parte de la imagen anterior
    AncAnt = int(Anchura * Porcentaje / 200)

    # La orden para crear el trozo superior derecho de la imagen anterior
    OrdenSupDerAnt = ('convert ' + Anterior +
                      ' -crop ' + str(AncAnt) + 'x' + str(AltAnt) +
                      '+' + str(int(Anchura/2)) +
                      '+' + str(int(Altura/2-AltAnt)) + ' ' +
                      TrozoSupDerAnt)

    # La orden para crear el trozo inferior derecho de la imagen anterior
    OrdenInfDerAnt = ('convert ' + Anterior +
                      ' -crop ' + str(AncAnt) + 'x' + str(AltAnt) +
                      '+' + str(int(Anchura/2)) +
                      '+' + str(int(Altura/2)) + ' ' +
                      TrozoInfDerAnt)

    # La orden para crear el trozo superior izquierdo de la imagen anterior
    OrdenSupIzqAnt = ('convert ' + Anterior +
                      ' -crop ' + str(AncAnt) + 'x' + str(AltAnt) +
                      '+' + str(int(Anchura/2-AncAnt)) +
                      '+' + str(int(Altura/2-AltAnt)) + ' ' +
                      TrozoSupIzqAnt)

    # La orden para crear el trozo inferior izquierdo de la imagen anterior
    OrdenInfIzqAnt = ('convert ' + Anterior +
                      ' -crop ' + str(AncAnt) + 'x' + str(AltAnt) +
                      '+' + str(int(Anchura/2-AncAnt)) +
                      '+' + str(int(Altura/2)) + ' ' +
                      TrozoInfIzqAnt)

    # La orden para unir el trozo superior derecho
    OrdenUnirSupDer = ('composite -gravity NorthEast ' + TrozoSupDerAnt + ' ' +
                       Siguiente + ' ' + Intermedio1)

    # La orden para unir el trozo inferior derecho
    OrdenUnirInfDer = ('composite -gravity SouthEast ' + TrozoInfDerAnt + ' ' +
                       Intermedio1 + ' ' + Intermedio2)

    # La orden para unir el trozo superior izquierdo
    OrdenUnirSupIzq = ('composite -gravity NorthWest ' + TrozoSupIzqAnt + ' ' +
                       Intermedio2 + ' ' + Intermedio3)

    # La orden para unir el trozo inferior izquierdo
    OrdenUnirInfIzq = ('composite -gravity SouthWest ' + TrozoInfIzqAnt + ' ' +
                       Intermedio3 + ' ' + Nueva)

    # Montamos la orden completa
    OrdenFinal = ';'.join([OrdenSupDerAnt, OrdenInfDerAnt,
                 OrdenSupIzqAnt, OrdenInfIzqAnt, OrdenUnirSupDer,
                 OrdenUnirInfDer, OrdenUnirSupIzq, OrdenUnirInfIzq])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def poner(Info):
    # Tomamos las variables del argumento
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Porcentaje = Info['porcentaje']
    Parametro = Info['param1']

    # La transición poner es igual que la transición quitar
    # intercambiando Anterior y Siguiente y el porcentaje
    Anterior, Siguiente = Siguiente, Anterior
    Porcentaje = 100 - Porcentaje

    # Guardamos los nuevos valores de las variables en el argumento
    Info['anterior'] = Anterior
    Info['siguiente'] = Siguiente
    Info['porcentaje'] = Porcentaje

    # Construimos el nombre de la función que calculará la orden
    Funcion = eval('quitar_' + Parametro)

    # Devolvemos nosotros lo que diga la trasición quitar
    return Funcion(Info)

#------------------------------------------------
def bandasvert(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Direccion = Info['param1']
    NumBandas = int(Info['param2'])

    # Si nos mandan un parámetro 0, usamos 1 (lo mínimo posible)
    if not NumBandas: NumBandas = 1

    # El archivo que contendrá la máscara
    Mascara = DirAux + '/mascara-' + Formato4(Paso) + '.png'

    # Vemos si ya ha sido creado anteriormente
    ExisteMascara = os.path.exists(Mascara)

    # Calculamos la anchura que asignamos a cada banda
    AncBanda = Anchura / NumBandas

    # Calculamos las dimensiones  que tendrá la parte de la imagen siguiente
    # en cada banda
    if Direccion in ('ns', 'sn'):
        AltSig = Altura - int(Altura * Porcentaje / 100)
        AncSig = AncBanda
    if Direccion in ('oe', 'eo'):
        AltSig = Altura
        AncSig = AncBanda - int(AncBanda * Porcentaje / 100)

    # El archivo que contendrá las órdenes de dibujo de rectángulos
    Dibujo = DirAux + '/dibujo' + Formato4(Paso) + '.mvg'
    Salida = open(Dibujo, 'w')
    Salida.write('viewbox 0 0 ' + str(Anchura) + ' ' + str(Altura) + '\n')
    Salida.write('fill #FFF rectangle 0 0 ' +
                 str(Anchura) + ' ' + str(Altura) + '\n')

    # Vamos dibujando un rectángulo por cada banda
    if Direccion in ('ns', 'oe', 'eo'): PosY = 0
    else: PosY = Altura-AltSig
    if Direccion in ('ns', 'sn', 'oe'): PosX = 0
    else: PosX = AncBanda-AncSig
    for i in range(NumBandas):
        Cadena = 'fill #000 rectangle '
        Cadena += str(i * AncBanda + PosX) + ' '
        Cadena += str(PosY) + ' '
        Cadena += str(i * AncBanda + AncSig + PosX - 1) + ' '
        Cadena += str(PosY + AltSig - 1)
        Salida.write(Cadena + '\n')
        if Direccion in ('ns', 'sn'):
            if PosY == 0: PosY = Altura-AltSig
            else: PosY = 0
    # Cerramos el fichero
    Salida.close()

    # La orden para crear la máscara para componer las imágenes
    OrdenMascara = 'convert mvg:' + Dibujo + ' -blur 0x1 ' + Mascara

    # La orden para componer las imágenes
    OrdenComponer = ('convert ' + Siguiente + ' ' + Anterior + ' ' + Mascara +
                     ' -composite ' + Nueva)

    # Decidimos qué orden devolver
    if ExisteMascara: OrdenFinal = OrdenComponer
    else: OrdenFinal = OrdenMascara + ';' + OrdenComponer

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def bandashori(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    Porcentaje = Info['porcentaje']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Direccion = Info['param1']
    NumBandas = int(Info['param2'])

    # Si nos mandan un parámetro 0, usamos 1 (lo mínimo posible)
    if not NumBandas: NumBandas = 1

    # El archivo que contendrá la máscara
    Mascara = DirAux + '/mascara-' + Formato4(Paso) + '.png'

    # Vemos si ya ha sido creado anteriormente
    ExisteMascara = os.path.exists(Mascara)

    # Calculamos la altura que asignamos a cada banda
    AltBanda = Altura / NumBandas

    # Calculamos las dimensiones  que tendrá la parte de la imagen siguiente
    # en cada banda
    if Direccion in ('ns', 'sn'):
        AltSig = AltBanda - int(AltBanda * Porcentaje / 100)
        AncSig = Anchura
    if Direccion in ('oe', 'eo'):
        AltSig = AltBanda
        AncSig = Anchura - int(Anchura * Porcentaje / 100)

    # El archivo que contendrá las órdenes de dibujo de rectángulos
    Dibujo = DirAux + '/dibujo' + Formato4(Paso) + '.mvg'
    Salida = open(Dibujo, 'w')
    Salida.write('viewbox 0 0 ' + str(Anchura) + ' ' + str(Altura) + '\n')
    Salida.write('fill #FFF rectangle 0 0 ' +
                 str(Anchura) + ' ' + str(Altura) + '\n')

    # Vamos dibujando un rectángulo por cada banda
    if Direccion in ('ns', 'oe', 'eo'): PosY = 0
    else: PosY = AltBanda-AltSig
    if Direccion in ('ns', 'sn', 'oe'): PosX = 0
    else: PosX = Anchura-AncSig
    for i in range(NumBandas):
        Cadena = 'fill #000 rectangle '
        Cadena += str(PosX) + ' '
        Cadena += str(i * AltBanda + PosY) + ' '
        Cadena += str(AncSig + PosX - 1) + ' '
        Cadena += str(i * AltBanda + PosY + AltSig - 1)
        Salida.write(Cadena + '\n')
        if Direccion in ('oe', 'eo'):
            if PosX == 0: PosX = Anchura-AncSig
            else: PosX = 0
    # Cerramos el fichero
    Salida.close()

    # La orden para crear la máscara para componer las imágenes
    OrdenMascara = 'convert mvg:' + Dibujo + ' -blur 0x1 ' + Mascara

    # La orden para componer las imágenes
    OrdenComponer = ('convert ' + Siguiente + ' ' + Anterior + ' ' + Mascara +
                     ' -composite ' + Nueva)

    # Decidimos qué orden devolver
    if ExisteMascara: OrdenFinal = OrdenComponer
    else: OrdenFinal = OrdenMascara + ';' + OrdenComponer

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def abanico(Info):
    # El ángulo total en grados que debemos girar en cada vértice
    AngTotalVert = {'c': 360, 'n': 180, 's': 180, 'e': 180, 'o': 180,
       'no': 90, 'so': 90, 'ne': 90, 'se': 90}

    # El ángulo inicial en grados en que comenzamos el giro en cada vértice
    AngInicialVert = { 'c': {'pos': 0,'neg': 0},
        'n': {'pos': 180, 'neg': 0}, 's': {'pos': 0,'neg': 180},
        'o': {'pos': -90, 'neg': 90},'e': {'pos': 90,'neg': -90},
        'no': {'pos': -90, 'neg': 0},   'so': {'pos': 0,'neg': 90},
        'ne': {'pos': 180, 'neg': -90}, 'se': {'pos': 90,'neg': 180}
        }

    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    DirAux = Info['diraux']
    Total = Info['total']
    Paso = Info['paso']
    Vertice = Info['param1']
    Zonas = int(Info['param2'])
    Sentido = Info['param3']

    # Si nos mandan un número de zonas 0 es que solo usamos una
    if not Zonas: Zonas = 1

    # El ángulo total que debemos girar
    AnguloTotal =  math.radians(AngTotalVert[Vertice])
    if Sentido == 'neg': AnguloTotal *= -1

    # El ángulo inicial en que comenzamos el giro
    AnguloInicial = math.radians(AngInicialVert[Vertice][Sentido])

    # El ángulo máximo que hay que girar en cada zona
    AnguloZona = AnguloTotal / Zonas

    # El ángulo que hay que girar en cada zona en este paso
    AnguloPaso = (Paso+1) * AnguloZona / (Total+1)
    AtencionAnguloGrande = math.fabs(AnguloPaso) > 1.5

    # El radio para calcular los puntos que definen cada triángulo;
    #     no es un valor muy alto y cubre todas las posibilidades
    Radio = Anchura + Altura

    # Las coordenadas del punto inicial
    if Vertice == 'no': OrigenX = 0         ; OrigenY = 0
    if Vertice == 'n':  OrigenX = Anchura/2 ; OrigenY = 0
    if Vertice == 'ne': OrigenX = Anchura-1 ; OrigenY = 0
    if Vertice == 'o':  OrigenX = 0         ; OrigenY = Altura/2
    if Vertice == 'c':  OrigenX = Anchura/2 ; OrigenY = Altura/2
    if Vertice == 'e':  OrigenX = Anchura-1 ; OrigenY = Altura/2
    if Vertice == 'so': OrigenX = 0         ; OrigenY = Altura-1
    if Vertice == 's':  OrigenX = Anchura/2 ; OrigenY = Altura-1
    if Vertice == 'se': OrigenX = Anchura-1 ; OrigenY = Altura-1

    # El archivo que contendrá la máscara
    Mascara = DirAux + '/mascara-' + Formato4(Paso) + '.png'

    # Vemos si ya ha sido creado anteriormente
    ExisteMascara = os.path.exists(Mascara)

    # La orden para crear la máscara para componer las imágenes
    OrdenMascara = ('convert -size ' + str(Anchura) + 'x' + str(Altura) +
                    ' xc:black -fill white ')
    for z in range(Zonas):
        # En cada zona pintamos, en principio, un triángulo
        # Primer punto del triángulo: el vértice del giro
        OrdenMascara += '-draw "polygon ' + str(OrigenX) + ',' + str(OrigenY)
        # El segundo punto, en el ángulo inicial de cada zona
        Angulo = AnguloInicial + z * AnguloZona
        X = OrigenX + Radio * math.cos(Angulo)
        Y = OrigenY - Radio * math.sin(Angulo)
        OrdenMascara += ' ' + str(X) + ',' + str(Y)
        # Si el ángulo que hay que girar ahora es muy grande, un triángulo no
        #     valdría, así que usamos unos ángulos auxiliares
        if AtencionAnguloGrande:
            AnguloAuxiliar = 0
            while math.fabs(AnguloPaso-AnguloAuxiliar)>1.5:
                if Sentido == 'pos': AnguloAuxiliar += 1
                else:                AnguloAuxiliar -= 1
                X = OrigenX + Radio * math.cos(Angulo+AnguloAuxiliar)
                Y = OrigenY - Radio * math.sin(Angulo+AnguloAuxiliar)
                OrdenMascara += ' ' + str(X) + ',' + str(Y)
        # El tercer punto, en el ángulo final de cada zona
        Angulo += AnguloPaso
        X = OrigenX + Radio * math.cos(Angulo)
        Y = OrigenY - Radio * math.sin(Angulo)
        OrdenMascara += ' ' + str(X) + ',' + str(Y) + '" '
    OrdenMascara += '-blur 0x2 ' + Mascara

    # La orden para componer las imágenes
    OrdenComponer = ('convert ' + Anterior + ' ' + Siguiente + ' ' + Mascara +
                     ' -composite ' + Nueva)

    # Decidimos qué orden devolver
    if ExisteMascara: OrdenFinal = OrdenComponer
    else: OrdenFinal = OrdenMascara + ';' + OrdenComponer

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def triangulo(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Porcentaje = Info['porcentaje']
    Sentido = Info['param1']
    Angulo = int(Info['param2'])

    # Si nos mandan un ángulo 0, el valor real es 90°
    if not Angulo: Angulo = 90

    # Pasamos el ángulo a radianes
    Angulo = math.radians(Angulo)

    # El archivo que contendrá la máscara
    Mascara = DirAux + '/mascara-' + Formato4(Paso) + '.png'

    # Vemos si ya ha sido creado anteriormente
    ExisteMascara = os.path.exists(Mascara)

    # Calculamos las dimensiones de un triángulo con el ángulo dado
    # que cubra la imagen completa
    CteTrig = 2 * math.tan(Angulo/2)
    if Sentido in ('oe','eo'):
        BaseTriangulo = Anchura * CteTrig + Altura
        AlturaTriangulo = BaseTriangulo / CteTrig
        MovimientoCompleto = Anchura + Altura / CteTrig
    else:
        BaseTriangulo = Altura * CteTrig + Anchura
        AlturaTriangulo = BaseTriangulo / CteTrig
        MovimientoCompleto = Altura + Anchura / CteTrig

    # El movimiento relativo de este paso
    MovimientoRelativo = MovimientoCompleto * (100-Porcentaje) / 100

    # El triángulo tiene tres vértices: (X0,Y0), (X1,Y1) y (X2,Y2)
    # El vértice (X0,Y0) es el más visible

    if Sentido == 'oe':
        X0 = MovimientoRelativo
        Y0 = Altura / 2
        X1 = X0 - AlturaTriangulo
        Y1 = Y0 + BaseTriangulo / 2
        X2 = X0 - AlturaTriangulo
        Y2 = Y0 - BaseTriangulo / 2

    if Sentido == 'eo':
        X0 = Anchura - MovimientoRelativo
        Y0 = Altura / 2
        X1 = X0 + AlturaTriangulo
        Y1 = Y0 + BaseTriangulo / 2
        X2 = X0 + AlturaTriangulo
        Y2 = Y0 - BaseTriangulo / 2

    if Sentido == 'sn':
        X0 = Anchura / 2
        Y0 = Altura - MovimientoRelativo
        X1 = X0 + BaseTriangulo / 2
        Y1 = Y0 + AlturaTriangulo
        X2 = X0 - BaseTriangulo / 2
        Y2 = Y0 + AlturaTriangulo

    if Sentido == 'ns':
        X0 = Anchura / 2
        Y0 = MovimientoRelativo
        X1 = X0 + BaseTriangulo / 2
        Y1 = Y0 - AlturaTriangulo
        X2 = X0 - BaseTriangulo / 2
        Y2 = Y0 - AlturaTriangulo

    Poligono = (X0, Y0, X1, Y1, X2, Y2)

    # La orden para crear la máscara para componer las imágenes
    OrdenMascara = ('convert -size ' + str(Anchura) + 'x' + str(Altura) +
                    ' xc:black -fill white ')
    OrdenMascara += DibujaPoligono(Poligono)
    OrdenMascara += '-blur 0x2 ' + Mascara

    # La orden para componer las imágenes
    OrdenComponer = ('convert ' + Anterior + ' ' + Siguiente + ' ' + Mascara +
                     ' -composite ' + Nueva)

    # Decidimos qué orden devolver
    if ExisteMascara: OrdenFinal = OrdenComponer
    else: OrdenFinal = OrdenMascara + ';' + OrdenComponer

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def uves(Info):
    # Tomamos las variables del argumento
    Anchura = Info['anchura']
    Altura = Info['altura']
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Total = Info['total']
    Sentido = Info['param1']

    # El archivo que contendrá la máscara
    Mascara = DirAux + '/mascara-' + Formato4(Paso) + '.png'

    # Vemos si ya ha sido creado anteriormente
    ExisteMascara = os.path.exists(Mascara)

    # Esta transición tiene tres fases: 0, 1 y 2
    # Vemos en qué fase estamos
    Tercio = Total / 3
    if Paso < Tercio:
        Fase = 0
    elif Paso > 2*Tercio:
        Fase = 2
    else:
        Fase = 1

    # En la fase 1 dibujamos un pentágono que amplía el triángulo de la fase 0
    if Fase == 1:
        ColorFondo = 'black'; ColorFrente = 'white'
        Referencia = Anchura if Sentido in ('oe', 'eo') else Altura
        Avance = Referencia * (Paso-Tercio) / Tercio

        if Sentido == 'oe':
            X0 = 0
            Y0 = Altura
            X1 = Avance
            Y1 = Altura
            X2 = Anchura / 2
            Y2 = Altura / 2
            X3 = Avance
            Y3 = 0
            X4 = 0
            Y4 = 0

        if Sentido == 'eo':
            X0 = Anchura
            Y0 = Altura
            X1 = Anchura - Avance
            Y1 = Altura
            X2 = Anchura / 2
            Y2 = Altura / 2
            X3 = Anchura - Avance
            Y3 = 0
            X4 = Anchura
            Y4 = 0

        if Sentido == 'sn':
            X0 = 0
            Y0 = Altura
            X1 = 0
            Y1 = Altura - Avance
            X2 = Anchura / 2
            Y2 = Altura / 2
            X3 = Anchura
            Y3 = Altura - Avance
            X4 = Anchura
            Y4 = Altura

        if Sentido == 'ns':
            X0 = 0
            Y0 = 0
            X1 = 0
            Y1 = Avance
            X2 = Anchura / 2
            Y2 = Altura / 2
            X3 = Anchura
            Y3 = Avance
            X4 = Anchura
            Y4 = 0

        Poligono = (X0, Y0, X1, Y1, X2, Y2, X3, Y3, X4, Y4)

    else:
        AlturaTriangulo = Anchura if Sentido in ('oe', 'eo') else Altura
        # En la fase 0 dibujamos un triángulo que entra por un lateral
        if Fase == 0:
            AlturaTriangulo = AlturaTriangulo / 2 * (Paso+1) / Tercio
            ColorFondo = 'black'; ColorFrente = 'white'
        # En la fase 2 dibujamos un triángulo inverso que sale por un lateral
        else:
            AlturaTriangulo = AlturaTriangulo / 2 * (Total-Paso+1) / Tercio
            ColorFondo = 'white'; ColorFrente = 'black'

        if (Fase == 0 and Sentido == 'oe') or (Fase == 2 and Sentido == 'eo'):
            X0 = AlturaTriangulo
            Y0 = Altura / 2
            X1 = X0 - Anchura / 2
            Y1 = Y0 + Altura / 2
            X2 = X0 - Anchura / 2
            Y2 = Y0 - Altura / 2

        if (Fase == 0 and Sentido == 'eo') or (Fase == 2 and Sentido == 'oe'):
            X0 = Anchura - AlturaTriangulo
            Y0 = Altura / 2
            X1 = X0 + Anchura / 2
            Y1 = Y0 + Altura / 2
            X2 = X0 + Anchura / 2
            Y2 = Y0 - Altura / 2

        if (Fase == 0 and Sentido == 'ns') or (Fase == 2 and Sentido == 'sn'):
            X0 = Anchura / 2
            Y0 = AlturaTriangulo
            X1 = X0 + Anchura / 2
            Y1 = Y0 - Altura / 2
            X2 = X0 - Anchura / 2
            Y2 = Y0 - Altura / 2

        if (Fase == 0 and Sentido == 'sn')  or (Fase == 2 and Sentido == 'ns'):
            X0 = Anchura / 2
            Y0 = Altura - AlturaTriangulo
            X1 = X0 + Anchura / 2
            Y1 = Y0 + Altura / 2
            X2 = X0 - Anchura / 2
            Y2 = Y0 + Altura / 2

        Poligono = (X0, Y0, X1, Y1, X2, Y2)

    # La orden para crear la máscara para componer las imágenes
    OrdenMascara = ('convert -size ' + str(Anchura) + 'x' + str(Altura) +
                    ' xc:' + ColorFondo + ' -fill ' + ColorFrente)
    OrdenMascara += DibujaPoligono(Poligono)
    OrdenMascara += '-blur 0x2 ' + Mascara

    # La orden para componer las imágenes
    OrdenComponer = ('convert ' + Anterior + ' ' + Siguiente + ' ' + Mascara +
                     ' -composite ' + Nueva)

    # Decidimos qué orden devolver
    if ExisteMascara: OrdenFinal = OrdenComponer
    else: OrdenFinal = OrdenMascara + ';' + OrdenComponer

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def peano(Info):
    # Tomamos las variables del argumento
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Total = Info['total']
    Tamano = int(Info['param1'])
    Vertice = Info['param2']

    # Si nos mandan un tamaño 0 es que tenemos que calcular el MCD
    if not Tamano: Tamano = mcd(Anchura, Altura)

    # Cuántas filas y columnas vamos a usar
    MaxFil = Altura / Tamano
    MaxCol = Anchura / Tamano

    # La celda en que comenzamos la curva
    if Vertice == 'no': FilIni = 0        ; ColIni = 0
    if Vertice == 'n':  FilIni = 0        ; ColIni = MaxCol/2
    if Vertice == 'ne': FilIni = 0        ; ColIni = MaxCol-1
    if Vertice == 'o':  FilIni = MaxFil/2 ; ColIni = 0
    if Vertice == 'c':  FilIni = MaxFil/2 ; ColIni = MaxCol/2
    if Vertice == 'e':  FilIni = MaxFil/2 ; ColIni = MaxCol-1
    if Vertice == 'so': FilIni = MaxFil-1 ; ColIni = 0
    if Vertice == 's':  FilIni = MaxFil-1 ; ColIni = MaxCol/2
    if Vertice == 'se': FilIni = MaxFil-1 ; ColIni = MaxCol-1

    # El archivo que contendrá la máscara
    # Hay que escribir sobre él en cada paso
    Mascara = DirAux + '/mascara-' + Formato4(Paso) + '.png'

    # El archivo que contendrá las órdenes de dibujo de cuadrados
    Dibujo = DirAux + '/dibujo' + Formato4(Paso) + '.mvg'

    # Si es la primera vez que nos llaman, hay que crearlo todo
    if Paso == 0:

        # La orden para crear la lista de celdas tipo curva de Peano
        Orden = (EjecutablePeano + ' ' + str(MaxFil) + ' ' + str(MaxCol) +
                 ' ' + str(FilIni) + ' ' + str(ColIni))

        # Ejecutamos la orden y corregimos la expresión
        CadenaListaCeldas = os.popen(Orden).read()
        CadenaListaCeldas = CadenaListaCeldas.strip()
        CadenaListaCeldas = CadenaListaCeldas.replace(' ', ',')
        CadenaListaCeldas = '[' + CadenaListaCeldas + ']'

        # La lista con las celdas en que dividimos la imagen
        ListaCeldas = eval(CadenaListaCeldas)

        # Nos la guardamos en el objeto función
        peano.ListaCeldas = ListaCeldas

        # Vemos cuántas celdas hay que usar en cada paso
        # Lo usamos float para repartirlas más uniformemente
        CeldasCadaPaso = float(len(ListaCeldas)) / (Total+1)

        # Nos lo guardamos en el objeto función
        peano.CeldasCadaPaso = CeldasCadaPaso

        # Ponemos a cero los contadores de celdas usadas
        peano.CeldasUsadasInt = 0
        peano.CeldasUsadasFloat = 0.0

    # En cualquier paso, tenemos que seleccionar qué celdas vamos a modificar

    # Tomamos del objeto función los datos que calculamos en el paso 0
    CeldasCadaPaso = peano.CeldasCadaPaso
    ListaCeldas = peano.ListaCeldas
    CeldasUsadasInt = peano.CeldasUsadasInt
    CeldasUsadasFloat = peano.CeldasUsadasFloat

    # Vemos cuántas celdas vamos a usar en este paso
    CeldasUsadasFloat += CeldasCadaPaso
    NumCeldasEstePaso = int(CeldasUsadasFloat - CeldasUsadasInt)
    CeldasUsadasInt += NumCeldasEstePaso

    # Obtenemos la lista de celdas que vamos a usar
    ListaCeldasEstePaso = ListaCeldas[:CeldasUsadasInt]

    # Creamos la orden de dibujo
    Salida = open(Dibujo, 'w')
    Salida.write('viewbox 0 0 ' + str(Anchura) + ' ' + str(Altura) + '\n')
    Salida.write('fill #000 rectangle 0 0 ' +
                 str(Anchura) + ' ' + str(Altura) + '\n')

    # Dibujamos los cuadrados de las celdas de este paso
    for Fil, Col in ListaCeldasEstePaso:
        DibujaCuadrado(Salida, Fil, Col, Tamano)

    # Cerramos el fichero
    Salida.close()

    # Guardamos en el objeto función los nuevos valores
    peano.CeldasUsadasFloat = CeldasUsadasFloat
    peano.CeldasUsadasInt = CeldasUsadasInt

    # La orden para crear la máscara para componer las imágenes
    OrdenMascara = 'convert mvg:' + Dibujo + ' ' + Mascara

    # La orden para componer las imágenes
    OrdenComponer = ('convert ' + Anterior + ' ' + Siguiente + ' ' + Mascara +
                     ' -composite ' + Nueva)

    # Devolvemos la orden
    return OrdenMascara + ';' + OrdenComponer

#------------------------------------------------
def dosrec(Info):
    # Tomamos las variables del argumento
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Total = Info['total']
    Porcentaje = Info['porcentaje']
    Total = Info['total']
    TamanoIzq = int(Info['param1'])
    TranIzq = Info['param2']
    TranDer = Info['param3']

    # La orden final que devolveremos
    OrdenFinal = ''

    # Si nos mandan un tamaño 0, lo convertimos en media anchura
    if not TamanoIzq: TamanoIzq = Anchura/2

    # La parte derecha
    TamanoDer = Anchura - TamanoIzq

    # El archivo que contendrá la imagen izquierda anterior
    ImagenIzqAnt = DirAux + '/imagenIzqAnt.png'
    # El archivo que contendrá la imagen izquierda siguiente
    ImagenIzqSig = DirAux + '/imagenIzqSig.png'
    # El archivo que contendrá la imagen derecha anterior
    ImagenDerAnt = DirAux + '/imagenDerAnt.png'
    # El archivo que contendrá la imagen derecha siguiente
    ImagenDerSig = DirAux + '/imagenDerSig.png'

    # Si es la primera vez que nos llaman, hay que crear las imágenes
    # anterior y siguiente de la derecha y de la izquierda
    if Paso == 0:

        # La orden para crear la imagen izquierda anterior
        OrdenIzqAnt = ('convert ' + Anterior +
                       ' -crop ' + str(TamanoIzq) + 'x' + str(Altura) +
                       '+0+0 +repage ' + ImagenIzqAnt)
        # La orden para crear la imagen izquierda siguiente
        OrdenIzqSig = ('convert ' + Siguiente +
                       ' -crop ' + str(TamanoIzq) + 'x' + str(Altura) +
                       '+0+0 +repage ' + ImagenIzqSig)
        # La orden para crear la imagen derecha anterior
        OrdenDerAnt = ('convert ' + Anterior +
                       ' -crop ' + str(TamanoDer) + 'x' + str(Altura) +
                       '+' + str(TamanoIzq) + '+0 +repage ' + ImagenDerAnt)
        # La orden para crear la imagen derecha siguiente
        OrdenDerSig = ('convert ' + Siguiente +
                       ' -crop ' + str(TamanoDer) + 'x' + str(Altura) +
                       '+' + str(TamanoIzq) + '+0 +repage ' + ImagenDerSig)

        # Unimos todas las órdenes
        OrdenFinal = ';'.join([OrdenIzqAnt, OrdenIzqSig,
                               OrdenDerAnt, OrdenDerSig]) + ';'

    # Las imágenes que queremos que nos preparen las dos transiciones
    IntermediaIzq = DirAux + '/intermediaIzq' + Formato4(Paso) + '.png'
    IntermediaDer = DirAux + '/intermediaDer' + Formato4(Paso) + '.png'

    # Los directorios auxiliares para cada lado
    DirIzq = DirAux + '/izq'
    DirDer = DirAux + '/der'
    if not os.path.exists(DirIzq): os.mkdir(DirIzq)
    if not os.path.exists(DirDer): os.mkdir(DirDer)

    # Nombres de las funciones y parámetros que crearán las transiciones
    NombreFuncionIzq, Par1Izq, Par2Izq, Par3Izq = DosDifFuncionParam(TranIzq)
    NombreFuncionDer, Par1Der, Par2Der, Par3Der = DosDifFuncionParam(TranDer)

    # Algunas transiciones necesitan distinguir entre los dos lados
    if NombreFuncionIzq == 'mosaico' and NombreFuncionDer == 'mosaico':
        Par2Izq = 'izq' ; Par2Der = 'der'
    if NombreFuncionIzq == 'cascada' and NombreFuncionDer == 'cascada':
        Par3Izq = 'izq' ; Par3Der = 'der'
    if NombreFuncionIzq == 'mancha' and NombreFuncionDer == 'mancha':
        Par3Izq = 'izq' ; Par3Der = 'der'

    # Las funciones que crean las transiciones
    FuncionIzq = eval(NombreFuncionIzq)
    FuncionDer = eval(NombreFuncionDer)

    # La información que mandaremos a cada transición
    InfoIzq = {
      'anchura': TamanoIzq,
      'altura': Altura,
      'anterior': ImagenIzqAnt,
      'siguiente': ImagenIzqSig,
      'nueva': IntermediaIzq,
      'porcentaje': Porcentaje,
      'diraux': DirIzq,
      'paso': Paso,
      'total': Total,
      'param1': Par1Izq,
      'param2': Par2Izq,
      'param3': Par3Izq,
       }
    InfoDer = {
      'anchura': TamanoDer,
      'altura': Altura,
      'anterior': ImagenDerAnt,
      'siguiente': ImagenDerSig,
      'nueva': IntermediaDer,
      'porcentaje': Porcentaje,
      'diraux': DirDer,
      'paso': Paso,
      'total': Total,
      'param1': Par1Der,
      'param2': Par2Der,
      'param3': Par3Der,
       }


    # Las órdenes que crean las transiciones
    OrdenIzq = FuncionIzq(InfoIzq)
    OrdenDer = FuncionDer(InfoDer)

    # La orden que une las dos partes de la imagen
    OrdenUnir = ('convert +append ' + IntermediaIzq + ' ' + IntermediaDer +
                 ' ' + Nueva)

    # Unimos todas las órdenes
    OrdenFinal += ';'.join([OrdenIzq, OrdenDer, OrdenUnir])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def dosban(Info):
    # Tomamos las variables del argumento
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Total = Info['total']
    Porcentaje = Info['porcentaje']
    Total = Info['total']
    TamanoSup = int(Info['param1'])
    TranSup = Info['param2']
    TranInf = Info['param3']

    # La orden final que devolveremos
    OrdenFinal = ''

    # Si nos mandan un tamaño 0, lo convertimos en media altura
    if not TamanoSup: TamanoSup = Altura/2

    # La parte inferior
    TamanoInf = Altura - TamanoSup

    # El archivo que contendrá la imagen superior anterior
    ImagenSupAnt = DirAux + '/imagenSupAnt.png'
    # El archivo que contendrá la imagen superior siguiente
    ImagenSupSig = DirAux + '/imagenSupSig.png'
    # El archivo que contendrá la imagen inferior anterior
    ImagenInfAnt = DirAux + '/imagenInfAnt.png'
    # El archivo que contendrá la imagen inferior siguiente
    ImagenInfSig = DirAux + '/imagenInfSig.png'

    # Si es la primera vez que nos llaman, hay que crear las imágenes
    # anterior y siguiente de la superior y de la inferior
    if Paso == 0:

        # La orden para crear la imagen superior anterior
        OrdenSupAnt = ('convert ' + Anterior +
                       ' -crop ' + str(Anchura) + 'x' + str(TamanoSup) +
                       '+0+0 +repage ' + ImagenSupAnt)
        # La orden para crear la imagen superior siguiente
        OrdenSupSig = ('convert ' + Siguiente +
                       ' -crop ' + str(Anchura) + 'x' + str(TamanoSup) +
                       '+0+0 +repage ' + ImagenSupSig)
        # La orden para crear la imagen inferior anterior
        OrdenInfAnt = ('convert ' + Anterior +
                       ' -crop ' + str(Anchura) + 'x' + str(TamanoInf) +
                       '+0+' + str(TamanoSup) + ' +repage ' + ImagenInfAnt)
        # La orden para crear la imagen inferior siguiente
        OrdenInfSig = ('convert ' + Siguiente +
                       ' -crop ' + str(Anchura) + 'x' + str(TamanoInf) +
                       '+0+' + str(TamanoSup) + ' +repage ' + ImagenInfSig)

        # Unimos todas las órdenes
        OrdenFinal = ';'.join([OrdenSupAnt, OrdenSupSig,
                               OrdenInfAnt, OrdenInfSig]) + ';'

    # Las imágenes que queremos que nos preparen las dos transiciones
    IntermediaSup = DirAux + '/intermediaSup' + Formato4(Paso) + '.png'
    IntermediaInf = DirAux + '/intermediaInf' + Formato4(Paso) + '.png'

    # Los directorios auxiliares para cada banda
    DirSup = DirAux + '/sup'
    DirInf = DirAux + '/inf'
    if not os.path.exists(DirSup): os.mkdir(DirSup)
    if not os.path.exists(DirInf): os.mkdir(DirInf)

    # Nombres de las funciones y parámetros que crearán las transiciones
    NombreFuncionSup, Par1Sup, Par2Sup, Par3Sup = DosDifFuncionParam(TranSup)
    NombreFuncionInf, Par1Inf, Par2Inf, Par3Inf = DosDifFuncionParam(TranInf)

    # Algunas transiciones necesitan distinguir entre las dos bandas
    if NombreFuncionSup == 'mosaico' and NombreFuncionInf == 'mosaico':
        Par2Sup = 'sup' ; Par2Inf = 'inf'
    if NombreFuncionSup == 'cascada' and NombreFuncionInf == 'cascada':
        Par3Sup = 'sup' ; Par3Inf = 'inf'
    if NombreFuncionSup == 'mancha' and NombreFuncionInf == 'mancha':
        Par3Sup = 'sup' ; Par3Inf = 'inf'

    # Las funciones que crean las transiciones
    FuncionSup = eval(NombreFuncionSup)
    FuncionInf = eval(NombreFuncionInf)

    # La información que mandaremos a cada transición
    InfoSup = {
      'altura': TamanoSup,
      'anchura': Anchura,
      'anterior': ImagenSupAnt,
      'siguiente': ImagenSupSig,
      'nueva': IntermediaSup,
      'porcentaje': Porcentaje,
      'diraux': DirSup,
      'paso': Paso,
      'total': Total,
      'param1': Par1Sup,
      'param2': Par2Sup,
      'param3': Par3Sup,
       }
    InfoInf = {
      'altura': TamanoInf,
      'anchura': Anchura,
      'anterior': ImagenInfAnt,
      'siguiente': ImagenInfSig,
      'nueva': IntermediaInf,
      'porcentaje': Porcentaje,
      'diraux': DirInf,
      'paso': Paso,
      'total': Total,
      'param1': Par1Inf,
      'param2': Par2Inf,
      'param3': Par3Inf,
       }


    # Las órdenes que crean las transiciones
    OrdenSup = FuncionSup(InfoSup)
    OrdenInf = FuncionInf(InfoInf)

    # La orden que une las dos partes de la imagen
    OrdenUnir = ('convert -append ' + IntermediaSup + ' ' + IntermediaInf +
                 ' ' + Nueva)

    # Unimos todas las órdenes
    OrdenFinal += ';'.join([OrdenSup, OrdenInf, OrdenUnir])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
def dosdia(Info):
    # Tomamos las variables del argumento
    Anterior = Info['anterior']
    Siguiente = Info['siguiente']
    Nueva = Info['nueva']
    DirAux = Info['diraux']
    Paso = Info['paso']
    Total = Info['total']
    Porcentaje = Info['porcentaje']
    Total = Info['total']
    # Nombramos los cuatro cuadrantes con la notación habitual, con estas
    # equivalencias en el código:
    # C1: SupDer, C2: SupIzq, C3: InfIzq, C4: InfDer
    Tran24 = Info['param1']  # La transición de los cuadrantes 2 y 4
    Tran13 = Info['param2']  # La transición de los cuadrantes 1 y 3

    # La orden final que devolveremos
    OrdenFinal = ''

    # Los tamaños de los cuatro cuadrantes
    TamanoSup = Altura/2
    TamanoInf = Altura - TamanoSup
    TamanoIzq = Anchura/2
    TamanoDer = Anchura - TamanoIzq

    # El archivo que contendrá la imagen superior izquierda anterior
    ImagenSupIzqAnt = DirAux + '/imagenSupIzqAnt.png'
    # El archivo que contendrá la imagen superior derecha anterior
    ImagenSupDerAnt = DirAux + '/imagenSupDerAnt.png'
    # El archivo que contendrá la imagen superior izquierda siguiente
    ImagenSupIzqSig = DirAux + '/imagenSupIzqSig.png'
    # El archivo que contendrá la imagen superior derecha siguiente
    ImagenSupDerSig = DirAux + '/imagenSupDerSig.png'
    # El archivo que contendrá la imagen inferior izquierda anterior
    ImagenInfIzqAnt = DirAux + '/imagenInfIzqAnt.png'
    # El archivo que contendrá la imagen inferior derecha anterior
    ImagenInfDerAnt = DirAux + '/imagenInfDerAnt.png'
    # El archivo que contendrá la imagen inferior izquierda siguiente
    ImagenInfIzqSig = DirAux + '/imagenInfIzqSig.png'
    # El archivo que contendrá la imagen inferior derecha siguiente
    ImagenInfDerSig = DirAux + '/imagenInfDerSig.png'

    # Si es la primera vez que nos llaman, hay que crear las imágenes
    # anterior y siguiente de los cuatro cuadrantes
    if Paso == 0:

        # La orden para crear la imagen superior izquierda anterior
        OrdenSupIzqAnt = ('convert ' + Anterior +
                          ' -crop ' + str(TamanoIzq) + 'x' + str(TamanoSup) +
                          '+0+0 +repage ' + ImagenSupIzqAnt)
        # La orden para crear la imagen superior derecha anterior
        OrdenSupDerAnt = ('convert ' + Anterior +
                          ' -crop ' + str(TamanoDer) + 'x' + str(TamanoSup) +
                          '+' + str(TamanoIzq) +'+0 +repage ' +
                          ImagenSupDerAnt)
        # La orden para crear la imagen superior izquierda siguiente
        OrdenSupIzqSig = ('convert ' + Siguiente +
                          ' -crop ' + str(TamanoIzq) + 'x' + str(TamanoSup) +
                          '+0+0 +repage ' + ImagenSupIzqSig)
        # La orden para crear la imagen superior derecha siguiente
        OrdenSupDerSig = ('convert ' + Siguiente +
                          ' -crop ' + str(TamanoIzq) + 'x' + str(TamanoSup) +
                          '+' + str(TamanoIzq) +'+0 +repage ' +
                          ImagenSupDerSig)
        # La orden para crear la imagen inferior izquierda anterior
        OrdenInfIzqAnt = ('convert ' + Anterior +
                          ' -crop ' + str(TamanoIzq) + 'x' + str(TamanoInf) +
                          '+0+' + str(TamanoSup) + ' +repage ' +
                          ImagenInfIzqAnt)
        # La orden para crear la imagen inferior derecha anterior
        OrdenInfDerAnt = ('convert ' + Anterior +
                          ' -crop ' + str(TamanoDer) + 'x' + str(TamanoInf) +
                          '+' + str(TamanoIzq) + '+' + str(TamanoSup) +
                          ' +repage ' + ImagenInfDerAnt)
        # La orden para crear la imagen inferior izquierda siguiente
        OrdenInfIzqSig = ('convert ' + Siguiente +
                          ' -crop ' + str(TamanoIzq) + 'x' + str(TamanoInf) +
                          '+0+' + str(TamanoSup) + ' +repage ' +
                          ImagenInfIzqSig)
        # La orden para crear la imagen inferior derecha siguiente
        OrdenInfDerSig = ('convert ' + Siguiente +
                          ' -crop ' + str(TamanoIzq) + 'x' + str(TamanoInf) +
                          '+' + str(TamanoIzq) + '+' + str(TamanoSup) +
                          ' +repage ' + ImagenInfDerSig)

        # Unimos todas las órdenes
        OrdenFinal = ';'.join([OrdenSupIzqAnt, OrdenSupDerAnt, OrdenSupIzqSig,
                     OrdenSupDerSig, OrdenInfIzqAnt, OrdenInfDerAnt,
                     OrdenInfIzqSig, OrdenInfDerSig]) + ';'

    # Las imágenes que queremos que nos preparen las dos transiciones
    IntermediaSupIzq = DirAux + '/intermediaSupIzq' + Formato4(Paso) + '.png'
    IntermediaSupDer = DirAux + '/intermediaSupDer' + Formato4(Paso) + '.png'
    IntermediaInfIzq = DirAux + '/intermediaInfIzq' + Formato4(Paso) + '.png'
    IntermediaInfDer = DirAux + '/intermediaInfDer' + Formato4(Paso) + '.png'

    # Imágenes intermedias auxiliares
    IntermediaSup = DirAux + '/intermediaSup' + Formato4(Paso) + '.png'
    IntermediaInf = DirAux + '/intermediaInf' + Formato4(Paso) + '.png'

    # Los directorios auxiliares para cada cuadrante
    DirSupIzq = DirAux + '/supizq'
    DirSupDer = DirAux + '/supder'
    DirInfIzq = DirAux + '/infizq'
    DirInfDer = DirAux + '/infder'
    if not os.path.exists(DirSupIzq): os.mkdir(DirSupIzq)
    if not os.path.exists(DirSupDer): os.mkdir(DirSupDer)
    if not os.path.exists(DirInfIzq): os.mkdir(DirInfIzq)
    if not os.path.exists(DirInfDer): os.mkdir(DirInfDer)

    # Nombres de las funciones y parámetros que crearán las transiciones
    NombreFuncionC1, Par1C1, Par2C1, Par3C1 = DosDifFuncionParam(Tran13)
    NombreFuncionC2, Par1C2, Par2C2, Par3C2 = DosDifFuncionParam(Tran24)
    NombreFuncionC3, Par1C3, Par2C3, Par3C3 = DosDifFuncionParam(Tran13)
    NombreFuncionC4, Par1C4, Par2C4, Par3C4 = DosDifFuncionParam(Tran24)

    # Algunas transiciones necesitan distinguir entre los dos cuadrantes
    if NombreFuncionC1 == 'mosaico':
        Par2C1 = 'c1' ; Par2C3 = 'c3'
    if NombreFuncionC2 == 'mosaico':
        Par2C2 = 'c2' ; Par2C4 = 'c4'
    if NombreFuncionC1 == 'cascada':
        Par3C1 = 'c1' ; Par3C3 = 'c3'
    if NombreFuncionC2 == 'cascada':
        Par3C2 = 'c2' ; Par3C4 = 'c4'
    if NombreFuncionC1 == 'mancha':
        Par3C1 = 'c1' ; Par3C3 = 'c3'
    if NombreFuncionC2 == 'mancha':
        Par3C2 = 'c2' ; Par3C4 = 'c4'

    # Las funciones que crean las transiciones
    FuncionC1 = eval(NombreFuncionC1)
    FuncionC2 = eval(NombreFuncionC2)
    FuncionC3 = eval(NombreFuncionC3)
    FuncionC4 = eval(NombreFuncionC4)

    # La información que mandaremos a cada transición
    InfoSupIzq = {
      'altura': TamanoSup,
      'anchura': TamanoIzq,
      'anterior': ImagenSupIzqAnt,
      'siguiente': ImagenSupIzqSig,
      'nueva': IntermediaSupIzq,
      'porcentaje': Porcentaje,
      'diraux': DirSupIzq,
      'paso': Paso,
      'total': Total,
      'param1': Par1C2,
      'param2': Par2C2,
      'param3': Par3C2,
       }
    InfoSupDer = {
      'altura': TamanoSup,
      'anchura': TamanoDer,
      'anterior': ImagenSupDerAnt,
      'siguiente': ImagenSupDerSig,
      'nueva': IntermediaSupDer,
      'porcentaje': Porcentaje,
      'diraux': DirSupDer,
      'paso': Paso,
      'total': Total,
      'param1': Par1C1,
      'param2': Par2C1,
      'param3': Par3C1,
       }
    InfoInfIzq = {
      'altura': TamanoInf,
      'anchura': TamanoIzq,
      'anterior': ImagenInfIzqAnt,
      'siguiente': ImagenInfIzqSig,
      'nueva': IntermediaInfIzq,
      'porcentaje': Porcentaje,
      'diraux': DirInfIzq,
      'paso': Paso,
      'total': Total,
      'param1': Par1C3,
      'param2': Par2C3,
      'param3': Par3C3,
       }
    InfoInfDer = {
      'altura': TamanoInf,
      'anchura': TamanoDer,
      'anterior': ImagenInfDerAnt,
      'siguiente': ImagenInfDerSig,
      'nueva': IntermediaInfDer,
      'porcentaje': Porcentaje,
      'diraux': DirInfDer,
      'paso': Paso,
      'total': Total,
      'param1': Par1C4,
      'param2': Par2C4,
      'param3': Par3C4,
       }


    # Las órdenes que crean las transiciones
    OrdenSupIzq = FuncionC2(InfoSupIzq)
    OrdenSupDer = FuncionC1(InfoSupDer)
    OrdenInfIzq = FuncionC3(InfoInfIzq)
    OrdenInfDer = FuncionC4(InfoInfDer)

    # La orden que une las dos partes superiores de la imagen
    OrdenUnirSup = ('convert +append ' + IntermediaSupIzq + ' ' +
                    IntermediaSupDer + ' ' + IntermediaSup)
    # La orden que une las dos partes inferiores de la imagen
    OrdenUnirInf = ('convert +append ' + IntermediaInfIzq + ' ' +
                    IntermediaInfDer + ' ' + IntermediaInf)
    # La orden que une las dos partes de la imagen
    OrdenUnir = ('convert -append ' + IntermediaSup + ' ' + IntermediaInf +
                 ' ' + Nueva)

    # Unimos todas las órdenes
    OrdenFinal += ';'.join([OrdenSupIzq, OrdenSupDer, OrdenInfIzq, OrdenInfDer,
                            OrdenUnirSup, OrdenUnirInf, OrdenUnir])

    # Devolvemos la orden
    return OrdenFinal

#------------------------------------------------
# Funciones auxiliares para crear transiciones
#------------------------------------------------

#------------------------------------------------
# Escribe la orden de dibujar un cuadrado blanco en una celda
def DibujaCuadrado(Salida, Fil, Col, Tamano):
    # Si el tamaño es 1, hay que dibujar solo un píxel
    if Tamano == 1:
        Cadena = 'fill #FFF color '
        Cadena += str(Col * Tamano) + ' '
        Cadena += str(Fil * Tamano) + ' '
        Cadena += 'point'

    # Si el tamaño es mayor, hay que dibujar un cuadrado
    else:
        Cadena = 'fill #FFF rectangle '
        Cadena += str(Col * Tamano) + ' '
        Cadena += str(Fil * Tamano) + ' '
        Cadena += str((Col+1) * Tamano - 1) + ' '
        Cadena += str((Fil+1) * Tamano - 1)

    # Enviamos la orden
    Salida.write(Cadena + '\n')

#------------------------------------------------
# Escribe la parte de la orden de dibujar un polígono
def DibujaPoligono(Poligono):
    Orden = ' -draw "polygon'

    for i in range(len(Poligono)/2):
        Orden += ' ' + str(Poligono[2*i]) + ',' + str(Poligono[2*i+1])

    Orden += '" '

    return Orden

#------------------------------------------------
# Crea una lista con celdas que dividen la imagen en cuadrados
def CreaListaCeldas(Tamano, Anchura, Altura):
    Fil = Altura / Tamano
    Col = Anchura / Tamano
    return [(i,j) for i in range(Fil) for j in range(Col)]

#------------------------------------------------
# Calcula el máximo común divisor de dos números
def mcd(a, b):
    while b: a, b = b, a % b
    return a

#------------------------------------------------
# Devuelve un número como cadena de cuatro caracteres
def Formato4(Numero):
    return '%04d' % Numero

#------------------------------------------------
# Dice si un número divide a las dos dimensiones del vídeo
def Divide(Numero, Mensaje):
    # En principio, el número es válido
    Correcto = True

    # Aceptamos el cero porque es el valor por defecto
    if Numero:

        # El número debe ser divisor de la anchura
        if Anchura % Numero:
            print Mensaje, str(Numero), 'no es divisor de', str(Anchura)
            Correcto = False

        # El número debe ser divisor de la altura
        if Altura % Numero:
            print Mensaje, str(Numero), 'no es divisor de', str(Altura)
            Correcto = False

    return Correcto

#------------------------------------------------
# Dice si un número divide a la anchura del vídeo
def DivideAnchura(Numero, Mensaje):
    # En principio, el número es válido
    Correcto = True

    # Aceptamos el cero porque es el valor por defecto
    if Numero:

        # El número debe ser divisor de la anchura
        if Anchura % Numero:
            print Mensaje, str(Numero), 'no es divisor de', str(Anchura)
            Correcto = False

    return Correcto

#------------------------------------------------
# Dice si un número divide a la altura del vídeo
def DivideAltura(Numero, Mensaje):
    # En principio, el número es válido
    Correcto = True

    # Aceptamos el cero porque es el valor por defecto
    if Numero:

        # El número debe ser divisor de la altura
        if Altura % Numero:
            print Mensaje, str(Numero), 'no es divisor de', str(Altura)
            Correcto = False

    return Correcto

#------------------------------------------------
# Dice si un número es menor que la anchura del vídeo
def MenorQueAnchura(Numero, Mensaje):
    # En principio, el número es válido
    Correcto = True

    # Aceptamos el cero porque es el valor por defecto
    if Numero:

        # El número debe ser menor que la anchura
        if not Numero<Anchura:
            print Mensaje, str(Numero), 'no es menor que', str(Anchura)
            Correcto = False

        # El número debe ser positivo
        if Numero<0:
            print Mensaje, str(Numero), 'no es positivo'
            Correcto = False

    return Correcto

#------------------------------------------------
# Dice si un número es menor que la altura del vídeo
def MenorQueAltura(Numero, Mensaje):
    # En principio, el número es válido
    Correcto = True

    # Aceptamos el cero porque es el valor por defecto
    if Numero:

        # El número debe ser menor que la altura
        if not Numero<Altura:
            print Mensaje, str(Numero), 'no es menor que', str(Altura)
            Correcto = False

        # El número debe ser positivo
        if Numero<0:
            print Mensaje, str(Numero), 'no es positivo'
            Correcto = False

    return Correcto

#------------------------------------------------
# Dice si un número es natural
def Natural(Numero, Mensaje):
    # En principio, el número es válido
    Correcto = True

    # Aceptamos el cero porque es el valor por defecto
    if Numero:

        # El número debe ser positivo
        if Numero<0:
            print Mensaje, str(Numero), 'no es un número natural'
            Correcto = False

    return Correcto

#------------------------------------------------
# Dice si un ángulo es válido para la transición 'triangulo'
def AnguloValido(Numero, Mensaje):
    # En principio, el número es válido
    Correcto = True

    # Aceptamos el cero porque es el valor por defecto
    if Numero:

        # El número debe ser positivo y menor que 180
        if Numero<0 or Numero>=180:
            print Mensaje, str(Numero), 'debe ser mayor que 0 y menor que 180'
            Correcto = False

    return Correcto

#------------------------------------------------
# Devuelve el nombre de la función y los parámetros reales que ayudarán
# a las transiciones dosrec, dosban y dosdia
def DosDifFuncionParam(Tran):
    if   Tran == 'mezcla': return 'mezcla', None, None, None

    elif Tran == 'luz-c':    return 'luz', 'claro', None, None
    elif Tran == 'luz-o':    return 'luz', 'oscuro', None, None

    elif Tran == 'rec-df': return 'rectangulo', 'df', None, None
    elif Tran == 'rec-fd': return 'rectangulo', 'fd', None, None

    elif Tran == 'aleatorio': return 'aleatorio', '4', None, None

    elif Tran == 'mosaico': return 'mosaico', '4', None, None

    elif Tran == 'cor-ns': return 'cortina', 'ns', None, None
    elif Tran == 'cor-sn': return 'cortina', 'sn', None, None
    elif Tran == 'cor-eo': return 'cortina', 'eo', None, None
    elif Tran == 'cor-oe': return 'cortina', 'oe', None, None

    elif Tran == 'aba-c': return 'abanico', 'c', '1', 'pos'
    elif Tran == 'aba-no': return 'abanico', 'no', '1', 'pos'
    elif Tran == 'aba-ne': return 'abanico', 'ne', '1', 'pos'
    elif Tran == 'aba-so': return 'abanico', 'so', '1', 'pos'
    elif Tran == 'aba-se': return 'abanico', 'se', '1', 'pos'
    elif Tran == 'aba-n': return 'abanico', 'n', '1', 'pos'
    elif Tran == 'aba-s': return 'abanico', 's', '1', 'pos'
    elif Tran == 'aba-e': return 'abanico', 'e', '1', 'pos'
    elif Tran == 'aba-o': return 'abanico', 'o', '1', 'pos'

    elif Tran == 'qui-s': return 'quitar_s', None, None, None
    elif Tran == 'qui-n': return 'quitar_n', None, None, None
    elif Tran == 'qui-e': return 'quitar_e', None, None, None
    elif Tran == 'qui-o': return 'quitar_o', None, None, None
    elif Tran == 'qui-se': return 'quitar_se', None, None, None
    elif Tran == 'qui-ne': return 'quitar_ne', None, None, None
    elif Tran == 'qui-so': return 'quitar_so', None, None, None
    elif Tran == 'qui-no': return 'quitar_no', None, None, None
    elif Tran == 'qui-ns': return 'quitar_ns', None, None, None
    elif Tran == 'qui-sn': return 'quitar_sn', None, None, None
    elif Tran == 'qui-eo': return 'quitar_eo', None, None, None
    elif Tran == 'qui-oe': return 'quitar_oe', None, None, None
    elif Tran == 'qui-v': return 'quitar_v', None, None, None

    elif Tran == 'pon-s': return 'poner', 's', None, None
    elif Tran == 'pon-n': return 'poner', 'n', None, None
    elif Tran == 'pon-e': return 'poner', 'e', None, None
    elif Tran == 'pon-o': return 'poner', 'o', None, None
    elif Tran == 'pon-se': return 'poner', 'se', None, None
    elif Tran == 'pon-ne': return 'poner', 'ne', None, None
    elif Tran == 'pon-so': return 'poner', 'so', None, None
    elif Tran == 'pon-no': return 'poner', 'no', None, None
    elif Tran == 'pon-ns': return 'poner', 'ns', None, None
    elif Tran == 'pon-sn': return 'poner', 'sn', None, None
    elif Tran == 'pon-eo': return 'poner', 'eo', None, None
    elif Tran == 'pon-oe': return 'poner', 'oe', None, None
    elif Tran == 'pon-v': return 'poner', 'v', None, None

    elif Tran == 'emp-sn': return 'empuja_sn', None, None, None
    elif Tran == 'emp-ns': return 'empuja_ns', None, None, None
    elif Tran == 'emp-oe': return 'empuja_oe', None, None, None
    elif Tran == 'emp-eo': return 'empuja_eo', None, None, None

    elif Tran == 'apa-c': return 'aparece', 'c', None, None
    elif Tran == 'apa-n': return 'aparece', 'n', None, None
    elif Tran == 'apa-s': return 'aparece', 's', None, None
    elif Tran == 'apa-o': return 'aparece', 'o', None, None
    elif Tran == 'apa-e': return 'aparece', 'e', None, None
    elif Tran == 'apa-no': return 'aparece', 'no', None, None
    elif Tran == 'apa-ne': return 'aparece', 'ne', None, None
    elif Tran == 'apa-so': return 'aparece', 'so', None, None
    elif Tran == 'apa-se': return 'aparece', 'se', None, None

    elif Tran == 'des-c': return 'desaparece', 'c', None, None
    elif Tran == 'des-n': return 'desaparece', 'n', None, None
    elif Tran == 'des-s': return 'desaparece', 's', None, None
    elif Tran == 'des-o': return 'desaparece', 'o', None, None
    elif Tran == 'des-e': return 'desaparece', 'e', None, None
    elif Tran == 'des-no': return 'desaparece', 'no', None, None
    elif Tran == 'des-ne': return 'desaparece', 'ne', None, None
    elif Tran == 'des-so': return 'desaparece', 'so', None, None
    elif Tran == 'des-se': return 'desaparece', 'se', None, None

    elif Tran == 'cas-ns': return 'cascada', 'ns', '4', None
    elif Tran == 'cas-sn': return 'cascada', 'sn', '4', None
    elif Tran == 'cas-oe': return 'cascada', 'oe', '4', None
    elif Tran == 'cas-eo': return 'cascada', 'eo', '4', None
    elif Tran == 'cas-nose': return 'cascada', 'nose', '4', None
    elif Tran == 'cas-seno': return 'cascada', 'seno', '4', None
    elif Tran == 'cas-neso': return 'cascada', 'neso', '4', None
    elif Tran == 'cas-sone': return 'cascada', 'sone', '4', None

    elif Tran == 'cua-df': return 'cuadrados', 'df', '0', None
    elif Tran == 'cua-fd': return 'cuadrados', 'fd', '0', None

    elif Tran == 'cir-df': return 'circulos', 'df', '0', None
    elif Tran == 'cir-fd': return 'circulos', 'fd', '0', None

    elif Tran == 'man-a': return 'mancha', 'a', '4', None
    elif Tran == 'man-c': return 'mancha', 'c', '4', None
    elif Tran == 'man-n': return 'mancha', 'n', '4', None
    elif Tran == 'man-s': return 'mancha', 's', '4', None
    elif Tran == 'man-e': return 'mancha', 'e', '4', None
    elif Tran == 'man-o': return 'mancha', 'o', '4', None
    elif Tran == 'man-no': return 'mancha', 'no', '4', None
    elif Tran == 'man-ne': return 'mancha', 'ne', '4', None
    elif Tran == 'man-so': return 'mancha', 'so', '4', None
    elif Tran == 'man-se': return 'mancha', 'se', '4', None

    elif Tran == 'bv-ns': return 'bandasvert', 'ns', '2', None
    elif Tran == 'bv-sn': return 'bandasvert', 'sn', '2', None
    elif Tran == 'bv-oe': return 'bandasvert', 'oe', '2', None
    elif Tran == 'bv-eo': return 'bandasvert', 'eo', '2', None

    elif Tran == 'bh-ns': return 'bandashori', 'ns', '2', None
    elif Tran == 'bh-sn': return 'bandashori', 'sn', '2', None
    elif Tran == 'bh-oe': return 'bandashori', 'oe', '2', None
    elif Tran == 'bh-eo': return 'bandashori', 'eo', '2', None

    elif Tran == 'tri-ns': return 'triangulo', 'ns', '0', None
    elif Tran == 'tri-sn': return 'triangulo', 'sn', '0', None
    elif Tran == 'tri-oe': return 'triangulo', 'oe', '0', None
    elif Tran == 'tri-eo': return 'triangulo', 'eo', '0', None

    elif Tran == 'uves-ns': return 'uves', 'ns', '0', None
    elif Tran == 'uves-sn': return 'uves', 'sn', '0', None
    elif Tran == 'uves-oe': return 'uves', 'oe', '0', None
    elif Tran == 'uves-eo': return 'uves', 'eo', '0', None

#------------------------------------------------
# Las funciones que generan las órdenes para los efectos,
# que reciben el mismo nombre que el propio efecto
#------------------------------------------------

#------------------------------------------------
def difuminar(Archivo, Porcentaje, Nueva):
    return 'convert ' + Archivo + ' -blur 0x' + str(Porcentaje) + ' ' + Nueva

#------------------------------------------------
def bn(Archivo, Porcentaje, Nueva):
    # En este efecto los valores están cambiados:
    # 100 deja la imagen como estaba originalmente
    Porcentaje = 100 - Porcentaje

    # El valor 0 da un error cuando se genera un png, así que lo evitamos
    if Porcentaje == 0: Porcentaje = 0.001

    return ('convert ' + Archivo + ' -modulate 100,' + str(Porcentaje) +
             ' ' + Nueva)

#------------------------------------------------
# Las funciones necesarias para leer y procesar
# el archivo de definición del vídeo
#------------------------------------------------

#------------------------------------------------
# Analiza la línea de órdenes con la que nos han invocado
def AnalizaInvocacion():
    # El diccionario en el que dejaremos los datos leídos
    Invocacion = {'archivo': False, 'informacion': False,
                  'comprobacion': False, 'generacion': False,
                  'ordenes': False, 'vacia': True}

    # Analizamos cada argumento de la orden
    for Arg in sys.argv[1:]:

        # Si comienza con un guión, es una opción
        if Arg[0] == '-':

            # Nos quedamos con qué nos piden
            Opcion = Arg[1:]

            # Estudiamos cada caracter que nos manden
            for Letra in Opcion:

                # Vamos actuando en consecuencia según sea lo que quieren
                if Letra in 'iI':
                    Invocacion['informacion'] = True
                    Invocacion['vacia'] = False

                elif Letra in 'cC':
                    Invocacion['comprobacion'] = True
                    Invocacion['vacia'] = False

                elif Letra in 'gG':
                    Invocacion['generacion'] = True
                    Invocacion['vacia'] = False

                elif Letra in 'oO':
                    Invocacion['ordenes'] = True
                    Invocacion['vacia'] = False

                elif Letra in 'tT':
                     Invocacion['informacion'] = True
                     Invocacion['comprobacion'] = True
                     Invocacion['generacion'] = True
                     Invocacion['ordenes'] = True
                     Invocacion['vacia'] = False

                else:
                    print u'Aviso: opción', Letra, 'no reconocida'
                    Invocacion['vacia'] = False

        # Si no comienza con un guión, tiene que ser el nombre del archivo
        else:
            # Lo tomamos
            Invocacion['archivo'] = Arg

    # Devolvemos la información
    return Invocacion

#------------------------------------------------
# Muestra ayuda al usuario
def MostrarAyuda():
    print '  El programa se invoca con el nombre de un archivo y opciones'
    print '  Las opciones se invocan con un guion y una letra'
    print '  Se pueden unir varias opciones con un único guion'
    print '  -i: Información del archivo'
    print '  -c: Comprobación del archivo'
    print '  -g: Generación de las imágenes'
    print '  -o: Mostrar las órdenes'
    print '  -t: Todo lo anterior'

#------------------------------------------------
# Comprueba el nombre del archivo con la descripción
# del vídeo y actualiza algunas variables globales
def GestionaNombreArchivo(Archivo):
    global Clave, Dir

    # Si el archivo existe, modificamos las variables globales
    if os.path.exists(Archivo):
        print 'Archivo: ' + Archivo
        Clave = os.path.splitext(Archivo)[0]
        Dir = Clave
        Respuesta = True

    # Si no existe, informamos
    else:
        print 'Error: el archivo ' + Archivo + ' no existe'
        Respuesta = False

    # Contestamos
    return Respuesta

#------------------------------------------------
# Lee un archivo de descripción de vídeo
def LeeArchivo(Archivo):
    global FPS, Anchura, Altura, TodoBien

    # Abrimos el archivo
    with open(Archivo) as Fichero:
        # Leemos cada línea
        for i, Linea in enumerate(Fichero):
            # La limpiamos por los dos lados
            Linea = Linea.strip()
            # Nos quedamos con las líneas que tengan acciones
            if len(Linea) and Linea[0] != Comentario:
                # Nos quedemos con el número de línea
                NumLinea = str(i+1)
                # Preparamos un posible mensaje de error
                Mensaje = u'Error en la línea ' + NumLinea + ':'
                # Descomponemos la línea en componentes
                Datos = Linea.split()
                # Iteraremos por los datos según vaya siendo necesario
                Datos = iter(Datos)
                # El primer dato es el tipo de acción (y seguro que hay uno)
                Tipo = next(Datos)
                # Comprobamos que es una acción válida
                if not Tipo.lower() in AccionValida:
                    print Mensaje, u'acción', Tipo, 'no reconocida'
                    TodoBien = False
                else:
                    # Nos quedamos con el tipo de acción en minúsculas
                    Tipo = Tipo.lower()

                    # El siguiente dato es un número
                    try:
                        TextoNumero = next(Datos)
                        Numero = float(TextoNumero)
                        if Numero <= 0:
                            print Mensaje, 'el valor debe ser positivo'
                            TodoBien = False
                        # Almacenamos el valor
                        if   Tipo == 'fps': FPS = int(Numero)
                        elif Tipo == 'anchura': Anchura = int(Numero)
                        elif Tipo == 'altura': Altura = int(Numero)
                        else: LeeAccion(Tipo, Numero, Datos, NumLinea)
                    except StopIteration:
                        print Mensaje, u'falta el valor numérico'
                        TodoBien = False
                    except ValueError:
                        print Mensaje, u'número', TextoNumero, 'no reconocido'
                        TodoBien = False

#------------------------------------------------
# Lee una acción y la direcciona a quien la termine
def LeeAccion(Tipo, Numero, Datos, NumLinea):
    # Preparamos un posible mensaje de error
    Mensaje = u'Error en la línea ' + NumLinea + ':'

    # Empezamos a crear y rellenar una nueva acción
    Accion = {'tipo': Tipo, 'duracion': Numero, 'numlinea': NumLinea}

    # Mandamos cada tipo a la función que lo sabe interpretar
    if   Tipo == 'foto': LeeFoto(Accion, Datos, Mensaje)
    elif Tipo == 'tran': LeeTran(Accion, Datos, Mensaje)
    elif Tipo == 'movi': LeeMovi(Accion, Datos, Mensaje)
    elif Tipo == 'colo': LeeColo(Accion, Datos, Mensaje)
    elif Tipo == 'troz': LeeTroz(Accion, Datos, Mensaje)
    elif Tipo == 'efec': LeeEfec(Accion, Datos, Mensaje)

#------------------------------------------------
# Lee una acción de foto y la almacena
def LeeFoto(Accion, Datos, Mensaje):
    global ListaAccion, TodoBien

    # En principio vamos a poder terminar nuestra misión
    Sigue = True

    # El siguiente dato es el archivo de la foto
    try:
        Archivo = next(Datos)
        # Comprobamos que existe el archivo
        if not os.path.exists(Archivo):
            print Mensaje, 'no encuentro', Archivo
            Sigue = False
    except StopIteration:
        print Mensaje, 'falta el archivo de la foto'
        Sigue = False

    # La descrición en formato humano de esta imagen
    Descripcion = 'foto ' + Archivo

    # Nos aseguramos de mantener la continuidad
    Continuidad(imagen=Archivo, descripcion=Descripcion)

    if Sigue:
        # Anotamos el archivo
        Accion['archivo'] = Archivo
        # Almacenamos la acción
        ListaAccion.append(Accion)

    # Mensaje global para informar de problemas
    else: TodoBien = False

#------------------------------------------------
# Lee una acción de movimiento y la almacena
def LeeMovi(Accion, Datos, Mensaje):
    global ListaAccion, TodoBien

    # En principio vamos a poder terminar nuestra misión
    Sigue = True

    # Los movimientos no aportan ninguna imagen para transiciones
    Continuidad(accion=None, imagen=None, descripcion='')

    # El siguiente dato es el archivo de la imagen
    try:
        Archivo = next(Datos)
        # Comprobamos que existe el archivo
        if not os.path.exists(Archivo):
            print Mensaje, 'no encuentro', Archivo
            Sigue = False
    except StopIteration:
        print Mensaje, 'falta el archivo de la imagen'
        Sigue = False

    if Sigue:
        # El siguiente dato es la definición del movimiento
        try:
            Definicion = next(Datos)
            # Comprobamos que es válida
            if Definicion.count('-') != 7:
                print Mensaje, u'la definición del movimento no es correcta'
                Sigue = False
        except StopIteration:
            print Mensaje, u'falta la definición del movimento'
            Sigue = False

    if Sigue:
        # Anotamos el archivo
        Accion['archivo'] = Archivo
        # Anotamos la definición
        Accion['definicion'] = Definicion
        # Almacenamos la acción
        ListaAccion.append(Accion)

    # Mensaje global para informar de problemas
    else: TodoBien = False

#------------------------------------------------
# Lee una acción de transicion y la almacena
def LeeTran(Accion, Datos, Mensaje):
    global ListaAccion, TodoBien

    # En principio vamos a poder terminar nuestra misión
    Sigue = True

    # El siguiente dato es el nombre de la transición
    try:
        Transicion = next(Datos)
        # Comprobamos que existe la transición
        if not Transicion.lower() in ListaTran(0):
            print Mensaje, u'no se reconoce la transición', Transicion
            Sigue = False
    except StopIteration:
        print Mensaje, u'falta la transición'
        Sigue = False

    # Algunas transiciones pueden tener un parámetro
    if Sigue:
        if Transicion in ListaTran(1):
            Param1, Sigue = ExtraeParametro(0, Transicion, Datos, Mensaje)
        else: Param1 = ''

    # Algunas transiciones pueden tener un segundo parámetro
    if Sigue:
        if Transicion in ListaTran(2):
            Param2, Sigue = ExtraeParametro(1, Transicion, Datos, Mensaje)
        else: Param2 = ''

    # Algunas transiciones pueden tener un tercer parámetro
    if Sigue:
        if Transicion in ListaTran(3):
            Param3, Sigue = ExtraeParametro(2, Transicion, Datos, Mensaje)
        else: Param3 = ''

    if Sigue:
        # Anotamos la transición
        Accion['transicion'] = Transicion
        # Anotamos cuál era la imagen anterior
        Accion['anterior'] = Pendiente['imagen']
        Accion['descrant'] = Pendiente['descripcion']
        # Hemos agotado los datos pendientes
        Continuidad(accion=None, imagen=None, descripcion='')
        # Dejamos en blanco la imagen siguiente porque aún no la conocemos
        Accion['siguiente'] = None
        Accion['descrsig'] = ''
        # Anotamos los parámetros
        Accion['param1'] = Param1
        Accion['param2'] = Param2
        Accion['param3'] = Param3
        # Almacenamos la acción
        ListaAccion.append(Accion)
        # Anotamos que esta acción está incompleta
        Continuidad(accion=Accion)

    # Mensaje global para informar de problemas
    else: TodoBien = False

#------------------------------------------------
# Extrae un parámetro de una transición
def ExtraeParametro(i, Transicion, Datos, Mensaje):
    Sigue = True

    # Un valor para empezar
    Parametro = ''

    try: Parametro = next(Datos)
    except StopIteration: pass

    # Si el parámetro no ha aparecido, ponemos un valor por defecto
    if not Parametro:
        if InfoParamTran[Transicion][i]['tipo'] == 'texto':
            Parametro = InfoParamTran[Transicion][i]['valor'][0]
        if InfoParamTran[Transicion][i]['tipo'] == 'numero':
            Parametro = 0

    # Lo usamos en minúsculas
    if InfoParamTran[Transicion][i]['tipo'] == 'texto':
        Parametro = Parametro.lower()

    # Si el parámetro no es válido, informamos
    if InfoParamTran[Transicion][i]['tipo'] == 'texto':
        if Parametro not in InfoParamTran[Transicion][i]['valor']:
            print Mensaje, u'no se reconoce el parámetro', Parametro
            Sigue = False
    if InfoParamTran[Transicion][i]['tipo'] == 'numero':
        try: Parametro = int(Parametro)
        except ValueError:
            print Mensaje, u'número', Parametro, 'no reconocido'
            Sigue = False
        if Sigue:
            # Comprobamos que el número es válido para esta transición
            # La función que lo comprueba explicará posibles errores
            Funcion = eval(InfoParamTran[Transicion][i]['valor'])
            Sigue = Funcion(Parametro, Mensaje)

    return Parametro, Sigue

#------------------------------------------------
# Lee una acción de color y la almacena
def LeeColo(Accion, Datos, Mensaje):
    global ListaAccion, TodoBien

    # En principio vamos a poder terminar nuestra misión
    Sigue = True

    # El siguiente dato es el número hexadecimal del color
    try:
        Color = next(Datos)
        # Comprobamos que es un color válido
        if not (all(c in '0123456789abcdefABCDEF' for c in Color) and
                 len(Color) in (3,6)):
            print Mensaje, u'no se reconoce el color', Color
            Sigue = False
    except StopIteration:
        print Mensaje, u'falta el color'
        Sigue = False

    # El archivo de imagen con este color que generaremos
    Archivo = Dir + '/colo/' + Color + '.png'
    # La descrición en formato humano de esta imagen
    Descripcion = 'colo ' + Color
    # Nos aseguramos de mantener la continuidad
    Continuidad(imagen=Archivo, descripcion=Descripcion)

    if Sigue:
        # Anotamos el color
        Accion['color'] = Color
        # Anotamos el archivo
        Accion['archivo'] = Archivo
        # Almacenamos la acción
        ListaAccion.append(Accion)

    # Mensaje global para informar de problemas
    else: TodoBien = False

#------------------------------------------------
# Lee una acción de trozo y la almacena
def LeeTroz(Accion, Datos, Mensaje):
    global ListaAccion, TodoBien

    # En principio vamos a poder terminar nuestra misión
    Sigue = True

    # El siguiente dato es el archivo de la imagen
    try:
        Archivo = next(Datos)
        # Comprobamos que existe el archivo
        if not os.path.exists(Archivo):
            print Mensaje, 'no encuentro', Archivo
            Sigue = False
    except StopIteration:
        print Mensaje, 'falta el archivo de la imagen'
        Sigue = False

    if Sigue:
        # El siguiente dato es la definición del trozo
        try:
            Definicion = next(Datos)
            # Comprobamos que es válida
            if Definicion.count('-') != 3:
                print Mensaje, u'la definición del trozo no es correcta'
                Sigue = False
        except StopIteration:
            Definicion = ''
            print Mensaje, u'falta la definición del trozo'
            Sigue = False

    # La descrición en formato humano de esta imagen
    Descripcion = 'troz ' + Archivo + ' ' + Definicion

    # El archivo de imagen con este trozo que generaremos
    ArchivoTrozo = Dir + '/troz/' + Archivo + Definicion + '.png'

    # Nos aseguramos de mantener la continuidad
    Continuidad(imagen=ArchivoTrozo, descripcion=Descripcion)

    if Sigue:
        # Anotamos el archivo original
        Accion['archivo'] = Archivo
        # Anotamos el archivo con el trozo
        Accion['archivotrozo'] = ArchivoTrozo
        # Anotamos la definición
        Accion['definicion'] = Definicion
        # Almacenamos la acción
        ListaAccion.append(Accion)

    # Mensaje global para informar de problemas
    else: TodoBien = False

#------------------------------------------------
# Lee una acción de efecto y la almacena
def LeeEfec(Accion, Datos, Mensaje):
    global ListaAccion, TodoBien

    # En principio vamos a poder terminar nuestra misión
    Sigue = True

    # El siguiente dato es el archivo de la imagen
    try:
        Archivo = next(Datos)
        # Comprobamos que existe el archivo
        if not os.path.exists(Archivo):
            print Mensaje, 'no encuentro', Archivo
            Sigue = False
    except StopIteration:
        Archivo = ''
        print Mensaje, 'falta el archivo de la imagen'
        Sigue = False

    Efecto = ''
    if Sigue:
        # El siguiente dato es el nombre del efecto
        try:
            Efecto = next(Datos)
            # Comprobamos que es válido
            if Efecto.lower() not in EfectoValido:
                print Mensaje, 'no se reconoce el efecto', Efecto
                Sigue = False
        except StopIteration:
            Efecto = ''
            print Mensaje, 'falta el nombre del efecto'
            Sigue = False

    # Usamos el nombre del efecto en minúsculas
    Efecto = Efecto.lower()

    # Anotamos en la acción los datos que ya tenemos
    Accion['efecto'] = Efecto
    Accion['archivo'] = Archivo

    # Almacenamos la acción
    ListaAccion.append(Accion)

    # El siguente parámetro, opcional, indica si el efecto es directo o inverso
    # Directo: se comienza con la imagen original y se termina con la modificada
    # Inverso: se comienza con la imagen modificada y se termina con la original
    # Por defecto es directo
    Directo = True
    if Sigue:
        try:
            Texto = next(Datos).lower()
            # Comprobamos que es válido
            if Texto not in 'di':
                print Mensaje, 'no se reconoce el modo', Texto
                Sigue = False
            elif Texto == 'i': Directo = False
        except StopIteration: pass
    Accion['directo'] = Directo

    # Los efectos aportan una imagen inicial y una imagen final
    # que se pueden usar con las transiciones anterior y posterior
    NombreArchivo = HigienizaNombre(Archivo)
    Anterior = Dir + '/efec/' + Efecto + '-' + NombreArchivo + '-inicial.png'
    Siguiente = Dir + '/efec/' + Efecto + '-' + NombreArchivo + '-final.png'
    if not Directo: Anterior, Siguiente = Siguiente, Anterior
    Descripcion = 'efec ' + Efecto + ' ' + Archivo
    Continuidad(imagen=Anterior, descripcion=Descripcion)
    Continuidad(imagen=Siguiente, descripcion=Descripcion)

    # Guardamos en la acción los nombres de los archivos inicial y final
    Accion['imageninicial'] = Anterior
    Accion['imagenfinal'] = Siguiente

    # El siguiente parámetro, opcional, indica el tiempo que deben permanecer
    # en el vídeo las imágenes inicial y final
    TiempoInicial = TiempoFinal = 0
    if Sigue:
        try:
            Texto = next(Datos)
            # Comprobamos que es válido
            Info = Texto.split('-')
            if len(Info) != 2:
                print Mensaje, 'no se reconocen los tiempos', Texto
                Sigue = False
            else:
                try:
                    TiempoInicial = float(Info[0])
                    TiempoFinal = float(Info[1])
                except ValueError:
                    print Mensaje, 'no se reconocen los tiempos', Texto
                    TodoBien = False
        except StopIteration: pass
        if Accion['duracion'] <= TiempoInicial+TiempoFinal:
              print Mensaje, u'los tiempos suman más que la duración'
              TodoBien = False
    Accion['tiempoinicial'] = TiempoInicial
    Accion['tiempofinal'] = TiempoFinal

    # Mensaje global para informar de problemas
    if not Sigue: TodoBien = False

#------------------------------------------------
# Asegura que haya continuidad en las transiciones
def Continuidad(**Arg):
    global Pendiente

    # Anotamos los valores de las claves que nos manden
    for Clave in Arg:
        if Clave == 'accion': Pendiente['accion'] = Arg['accion']
        if Clave == 'imagen': Pendiente['imagen'] = Arg['imagen']
        if Clave == 'descripcion':
            Pendiente['descripcion'] = Arg['descripcion']

    # Si podemos, completamos una acción incompleta
    if ('imagen' in Arg and 'descripcion' in Arg and
         Pendiente['accion']):
         Pendiente['accion']['siguiente'] = Arg['imagen']
         Pendiente['accion']['descrsig'] = Arg['descripcion']
         Pendiente['accion'] = None

#------------------------------------------------
# Comprueba los datos de entrada
def CompruebaDatos():
    global TodoBien

    # Debemos tener definidas estas variables
    if not FPS:
        print u'Error: falta el número de cuadros por segundo (fps)'
        TodoBien = False
    if not Anchura:
        print u'Error: falta la anchura del vídeo'
        TodoBien = False
    if not Altura:
        print u'Error: falta la altura del vídeo'
        TodoBien = False

    # Debemos tener alguna acción anotada
    if not ListaAccion:
        print u'Error: no hay ninguna acción'
        TodoBien = False

    # Todas las transiciones deben estar completas
    for Accion in ListaTipo('tran'):
        # Tomamos los datos de la acción
        Anterior = Accion['anterior']
        Siguiente = Accion['siguiente']
        NumLinea = Accion['numlinea']

        # Comprobamos los datos
        if not Anterior:
            print u'Error en la línea', NumLinea + ': no hay imagen anterior'
            TodoBien = False
        if not Siguiente:
            print u'Error en la línea', NumLinea + ': no hay imagen siguiente'
            TodoBien = False

#------------------------------------------------
# Informa de los datos que tenemos
def InformaDatos():
    # El formato del vídeo
    print ('Formato del vídeo: ' + str(Anchura) + '⨉' +
            str(Altura) + '@' + str(FPS))

    # Número de acciones
    print 'Número de acciones:', len(ListaAccion)
    for Tipo in [Tipo for Tipo in AccionValida
                  if Tipo not in ('fps', 'anchura', 'altura')]:
        print ' ', Tipo + ':', len(ListaTipo(Tipo))

    # Calculamos la duración prevista del vídeo
    ListaDuracion = [Accion['duracion'] for Accion in ListaAccion]
    ListaDuracion = map(lambda d: int(d * FPS + 0.5), ListaDuracion)
    DuracionTotal = int((sum(ListaDuracion))/FPS+0.5)

    # Informamos
    if DuracionTotal > 60:
        MinSeg = ('[' + str(DuracionTotal/60) + ':' +
                  '%02d'%(DuracionTotal%60) + ']')
    else:
        MinSeg = ''
    print 'Duración prevista:', DuracionTotal, 'segundos', MinSeg

#------------------------------------------------
# Generación completa de todo lo necesario para crear el vídeo
def GeneracionCompleta():
    # Tomamos nota del comienzo de la generación
    Tiempo = time.time()

    # Preparamos el directorio para todo lo que necesitemos
    PreparaDirectorio()

    # Generamos todas las imágenes de los colores
    GeneraColores()

    # Generamos todas las imágenes de los trozos
    GeneraTrozos()

    # Generamos todas las imágenes extremas de los efectos
    GeneraExtremosEfectos()

    # Generamos todas las imágenes
    GeneraTodasLasImagenes()

    # Calculamos el tiempo empleado
    Tiempo = int(time.time() - Tiempo + 0.5)
    ExpresionTiempo = HumanizaTiempo(Tiempo)

    # Informamos
    print 'Tiempo empleado: ' + ExpresionTiempo


#------------------------------------------------
# La lista de acciones de un tipo determinado
def ListaTipo(Tipo):
    return [Accion for Accion in ListaAccion if Accion['tipo']==Tipo]

#------------------------------------------------
# La lista de transiciones que admiten n o más parámetros
def ListaTran(n):
    return [Tran for Tran in InfoParamTran.keys()
             if len(InfoParamTran[Tran])>=n]

#------------------------------------------------
# Las funciones que generan las imágenes
# necesarias para montar el vídeo
#------------------------------------------------

#------------------------------------------------
# Prepara el directorio propio para trabajar
def PreparaDirectorio():
    # Si no existe
    if not os.path.exists(Dir):
        # Lo creamos
        os.mkdir (Dir)

    # Si existe
    else:
        # Limpiamos el directorio con las imágenes finales
        shutil.rmtree(Dir + '/' + DirFinal)

#------------------------------------------------
# Genera todas las imágenes con colores
def GeneraColores():
    # Informamos
    print 'Generando colores...'

    # El directorio para guardar todas las imágenes de colores
    DirColo = Dir + '/colo'
    if not os.path.exists(DirColo): os.mkdir(DirColo)

    # Seleccionamos las acciones que piden colores
    ListaColo = ListaTipo('colo')

    # Vamos generando todos
    for Accion in ListaColo:
        # Tomamos los datos de la acción
        Color = Accion['color']
        Archivo = Accion['archivo']

        # Solo generamos el archivo si no existe
        if not os.path.exists(Archivo):
            # Informamos
            print ' ', Color

            # La orden para crear la imagen
            Orden = ('convert -size ' +
                     str(Anchura) + 'x' + str(Altura) + ' ' +
                     ' canvas:#' + Color + ' PNG32:' + Archivo)

            # La ejecutamos
            os.popen(Orden)

    # Informamos
    print 'Terminada la generación de colores.'

#------------------------------------------------
# Genera todas las imágenes con trozos
def GeneraTrozos():
    # Informamos
    print 'Generando trozos...'

    # El directorio para guardar todas las imágenes de trozos
    DirTroz = Dir + '/troz'
    if not os.path.exists(DirTroz): os.mkdir(DirTroz)

    # Seleccionamos las acciones que piden trozos
    ListaTroz = ListaTipo('troz')

    # Vamos generando todos
    for Accion in ListaTroz:
        # Tomamos los datos de la acción
        Archivo = Accion['archivo']
        ArchivoTrozo = Accion['archivotrozo']
        Definicion = Accion['definicion']

        # Solo generamos el archivo si no existe
        if not os.path.exists(ArchivoTrozo):
            # Informamos
            print ' ', Archivo, Definicion

            # Descomponemos la definición en sus componentes
            Definicion = Definicion.split('-')
            AncTrozo, AltTrozo, XTrozo, YTrozo = [x for x in Definicion]

            # La orden para crear la imagen con el trozo
            Orden = ('convert ' + Archivo +
                     ' -crop ' + AncTrozo + 'x' + AltTrozo +
                                 '+' + XTrozo + '+' + YTrozo +
                     ' -resize ' + str(Anchura) + 'x' + str(Altura) + '! ' +
                     ArchivoTrozo)

            # La ejecutamos
            os.popen(Orden)

    # Informamos
    print 'Terminada la generación de trozos.'

#------------------------------------------------
# Genera todas las imágenes extremas de los efectos
def GeneraExtremosEfectos():
    # Informamos
    print 'Generando los extremos de los efectos...'

    # El directorio para guardar todas las imágenes de los extremos de efectos
    DirEfec = Dir + '/efec'
    if not os.path.exists(DirEfec): os.mkdir(DirEfec)

    # Seleccionamos las acciones de efectos
    ListaEfec = ListaTipo('efec')

    # Vamos generando todos
    for Accion in ListaEfec:
        # Tomamos los datos de la acción
        Archivo = Accion['archivo']
        Efecto = Accion['efecto']
        Directo = Accion['directo']
        Imagenes = (Accion['imageninicial'], Accion['imagenfinal'])

        # La función que nos da la orden que crea el efecto
        Funcion = eval(Efecto)

        # Los valores que pasaremos a la función
        if Directo: Valores = (0,100)
        else: Valores = (100,0)

        for Imagen, Valor in zip(Imagenes, Valores):
            # Solo generamos los archivos si no existen
            if not os.path.exists(Imagen):
                # Informamos
                print ' ', Efecto, Archivo, Valor

                # La orden para crear la imagen con el efecto
                if Valor == 0: Orden = 'cp ' + Archivo + ' ' + Imagen
                else: Orden = Funcion(Archivo, Valor, Imagen)

                # La ejecutamos
                os.popen(Orden)

    # Informamos
    print 'Terminada la generación de los extremos de los efectos.'

#------------------------------------------------
# Genera todas las imágenes del vídeo
def GeneraTodasLasImagenes():
    # Creamos el directorio en el que dejar las imágenes finales
    os.mkdir(Dir + '/' + DirFinal)

    # El número de imagen final que estamos creando
    ImagenActual = 1

    # Informamos
    print 'Generando todas las imágenes... '

    # Recorremos todas las acciones
    TotalAccion = str(len(ListaAccion))
    for i, Accion in enumerate(ListaAccion):
        # Indicamos por dónde vamos
        print '  [' + str(i+1) + '/' + TotalAccion+ ']',

        # Vemos qué tenemos que hacer
        Tipo = Accion['tipo']

        # Vemos cuánto tiempo durará esta acción
        Duracion = Accion['duracion']

        # Calculamos cuántos cuadros hay que generar
        Cuadros = int(Duracion * FPS + 0.5)

        # Generamos los cuadros
        if Tipo == 'foto': GeneraFoto(Accion, ImagenActual, Cuadros)
        if Tipo == 'colo': GeneraColo(Accion, ImagenActual, Cuadros)
        if Tipo == 'tran': GeneraTran(Accion, ImagenActual, Cuadros)
        if Tipo == 'movi': GeneraMovi(Accion, ImagenActual, Cuadros)
        if Tipo == 'troz': GeneraTroz(Accion, ImagenActual, Cuadros)
        if Tipo == 'efec': GeneraEfec(Accion, ImagenActual, Cuadros)

        # Avanzamos
        ImagenActual += Cuadros

    # Informamos
    print 'Terminada la generación de todas las imágenes.'

#------------------------------------------------
# Genera las imágenes de una foto
def GeneraFoto (Accion, Primera, Total):
    # Tomamos el dato de la acción
    Archivo = Accion['archivo']

    # Informamos de que empezamos
    print 'foto', Archivo

    # Generamos todos los vínculos a la misma foto
    for i in range (Primera, Primera+Total):
        VinculaArchivo ('../../'+Archivo, i)

#------------------------------------------------
# Genera las imágenes de un color
def GeneraColo (Accion, Primera, Total):
    # Tomamos los datos de la acción
    Archivo = Accion['archivo']
    Color = Accion['color']

    # Informamos de que empezamos
    print 'colo', Color

    # Generamos todos los vínculos a la misma imagen
    for i in range (Primera, Primera+Total):
        VinculaArchivo ('../../'+Archivo, i)

#------------------------------------------------
# Genera las imágenes de un trozo
def GeneraTroz (Accion, Primera, Total):
    # Tomamos los datos de la acción
    Archivo = Accion['archivo']
    ArchivoTrozo = Accion['archivotrozo']
    Definicion = Accion['definicion']

    # Informamos de que empezamos
    print 'troz', Archivo, Definicion

    # Generamos todos los vínculos a la misma imagen
    for i in range (Primera, Primera+Total):
        VinculaArchivo ('../../'+ArchivoTrozo, i)

#------------------------------------------------
# Genera las imágenes de una transición
def GeneraTran (Accion, Primera, Total):
    # Tomamos los datos de la acción
    Nombre = Accion['transicion']
    Anterior = Accion['anterior']
    Siguiente = Accion['siguiente']
    DescrAnt = Accion['descrant']
    DescrSig = Accion['descrsig']
    Param1 = str(Accion['param1'])
    Param2 = str(Accion['param2'])
    Param3 = str(Accion['param3'])

    # Algunas transiciones necesitan mantener distintos datos
    mosaico.Datos = {}
    cascada.Datos = {}
    mancha.Datos = {}

    # Eliminamos caracteres incómodos
    AntLimpio = HigienizaNombre(Anterior)
    SigLimpio = HigienizaNombre(Siguiente)

    # El directorio para guardar las imágenes de esta transición
    DirTran = (Dir + '/tran-' + Nombre +
               Param1 + Param2 + Param3 + '-' +
               AntLimpio + '-' + SigLimpio + '-' + str(Total))

    # Si no existe, tenemos que crearlo y rellenarlo
    HayQueRellenarlo = True
    if not os.path.exists(DirTran):
        os.mkdir(DirTran)
    # Si ya existía, comprobamos si te terminó de generar correctamente
    elif os.path.exists(DirTran + '/' + Testigo):
        # Si terminó bien, solo habrá que regenerar los vínculos
        HayQueRellenarlo = False

    # Informamos de que empezamos
    NombreMasParametros = Nombre
    if Param1: NombreMasParametros += ' ' + Param1
    if Param2: NombreMasParametros += ' ' + Param2
    if Param3: NombreMasParametros += ' ' + Param3
    Descripcion = '[' + DescrAnt + '] [' + DescrSig + ']'
    Modo = '=> generando...' if HayQueRellenarlo else '=> reutilizando.'
    print 'tran', NombreMasParametros, Descripcion, Modo

    # Un directorio auxiliar para lo que pueda necesitar la transición
    # Por ejemplo, para almacenar máscaras; si no lo usa, quedará vacío
    DirAux = Dir + '/' + Nombre + Param1 + Param2 + Param3 + '-' + str(Total)
    if not os.path.exists(DirAux): os.mkdir(DirAux)

    # La función que nos dará las órdenes que hay que ejecutar
    # Posible problema en un futuro: solo tengo en cuenta el primer parámetro
    if Param1 and InfoParamTran[Nombre][0]['funcion']:
        NombreReal = Nombre + '_' + Param1
    else: NombreReal = Nombre
    Funcion = eval(NombreReal)

    # Creamos tantas imágenes como nos pidan
    for i in range(Total):
        # El nombre de la imagen que vamos a crear
        Nueva = DirTran + '/' + ('%04d' % i) + '.png'

        if HayQueRellenarlo:
            # El porcentaje de la imagen anterior
            Porcentaje = 100 * (1 - float(i+1)/(Total+1))

            # Preparamos el conjunto de variables para generar la nueva imagen
            # Se lo pasamos a todas las transiciones y que cada una use
            # lo que necesite
            Info = {
              'anchura': Anchura,       # La anchura de las imágenes
              'altura': Altura,         # La altura de las imágenes
              'anterior': Anterior,     # La imagen anterior
              'siguiente': Siguiente,   # La imagen siguiente
              'nueva': Nueva,           # La nueva imagen que hay que crear
              'porcentaje': Porcentaje, # El porcentaje de la primera
              'diraux': DirAux,         # Un directorio para hacer cosas
              'paso': i,                # El número de paso que damos
              'total': Total,           # El número total de pasos que daremos
              'param1': Param1,         # El primer parámetro de la transición
              'param2': Param2,         # El segundo parámetro de la transición
              'param3': Param3          # El tercer parámetro de la transición
              }

            # La orden que hay que ejecutar
            Orden = Funcion(Info)

            # La ejecutamos
            os.popen(Orden)

        # Creamos un vínculo a la imagen
        VinculaArchivo ('../../'+Nueva, Primera+i)

    # Como todo ha ido bien, creamos el archivo testigo
    open(DirTran + '/' + Testigo, 'a').close()

#------------------------------------------------
# Genera las imágenes de un movimiento
def GeneraMovi (Accion, Primera, Total):
    # Tomamos los datos de la acción
    Archivo = Accion['archivo']
    Definicion = Accion['definicion']

    # Informamos de que empezamos
    print 'movi', Archivo, Definicion

    # El directorio para guardar las imágenes de este movimiento
    DirMovi = (Dir + '/movi-' + Archivo + '-' + Definicion + '-' + str(Total))

    # Si no existe, tenemos que crearlo y rellenarlo
    if not os.path.exists(DirMovi):
        os.mkdir (DirMovi)
        HayQueRellenarlo = True
    # Si ya existía, solo habrá que regenerar los vínculos
    else:
        HayQueRellenarlo = False

    # Descomponemos la definición en sus componentes
    Definicion = Definicion.split('-')
    Anc1, Alt1, X1, Y1, Anc2, Alt2, X2, Y2 = [int(x) for x in Definicion]

    # Creamos tantas imágenes como nos pidan
    for i in range (Total):
        # El nombre de la imagen que vamos a crear
        Nueva = DirMovi + '/' + ('%04d' % i) + '.png'

        if HayQueRellenarlo:
            # Vamos a hacer interpolaciones con t entre 0 y 1
            t = float(i+1) / (Total+1)

            # Calculamos los datos del trozo que vamos a recortar
            AncTrozo = Interpola(Anc1, Anc2, t)
            AltTrozo = Interpola(Alt1, Alt2, t)
            XTrozo = Interpola(X1, X2, t)
            YTrozo = Interpola(Y1, Y2, t)

            # La orden que hay que ejecutar:
            # recortamos un trozo y cambiamos su tamaño
            Orden = ('convert ' + Archivo +
                     ' -crop ' + AncTrozo + 'x' + AltTrozo +
                                 '+' + XTrozo + '+' + YTrozo +
                     ' -resize ' + str(Anchura) + 'x' + str(Altura) + '! ' +
                     Nueva)

            # La ejecutamos
            os.popen(Orden)

        # Creamos un vínculo a la imagen
        VinculaArchivo ('../../'+Nueva, Primera+i)

#------------------------------------------------
# Genera las imágenes de un efecto
def GeneraEfec (Accion, Primera, Total):
    # Tomamos los datos de la acción
    Archivo = Accion['archivo']
    Directo = Accion['directo']
    Efecto = Accion['efecto']
    ImagenInicial = Accion['imageninicial']
    ImagenFinal = Accion['imagenfinal']
    TiempoInicial = Accion['tiempoinicial']
    TiempoFinal = Accion['tiempofinal']

    # Un efecto consta de tres fases, vemos cuántos cuadros tendrá cada una
    CuadrosInicial = int(FPS * TiempoInicial + 0.5)
    CuadrosFinal = int(FPS * TiempoFinal + 0.5)
    CuadrosMedio = Total - CuadrosInicial - CuadrosFinal

    # Informamos de que empezamos
    TextoDirecto = 'd' if Directo else 'i'
    print 'efec', Archivo, Efecto, TextoDirecto

    # El directorio para guardar las imágenes no fijas de este movimiento
    DirEfec = (Dir + '/efec-' + Efecto + '-' + Archivo + '-' + TextoDirecto +
              '-' + str(CuadrosMedio))

    # Si no existe, tenemos que crearlo y rellenarlo
    if not os.path.exists(DirEfec):
        os.mkdir (DirEfec)
        HayQueRellenarlo = True
    # Si ya existía, solo habrá que regenerar los vínculos
    else:
        HayQueRellenarlo = False

    # Generamos todos los vínculos a la imagen inicial
    for i in range (Primera, Primera+CuadrosInicial):
        VinculaArchivo ('../../'+ImagenInicial, i)
    Primera += CuadrosInicial

    # Creamos las imágenes del efecto propiamente dicho
    Funcion = eval(Efecto)
    for i in range(CuadrosMedio):
        # El nombre de la imagen que vamos a crear
        Nueva = DirEfec + '/' + ('%04d' % i) + '.png'

        if HayQueRellenarlo:
            # El porcentaje de efecto que hay que aplicar
            Porcentaje = 100 * float(i+1) / (CuadrosMedio+1)
            if not Directo: Porcentaje = 100 - Porcentaje

            # La orden que hay que ejecutar
            Orden = Funcion(Archivo, Porcentaje, Nueva)

            # La ejecutamos
            os.popen(Orden)

        # Creamos un vínculo a la imagen
        VinculaArchivo ('../../'+Nueva, Primera+i)
    Primera += CuadrosMedio

    # Generamos todos los vínculos a la imagen final
    for i in range (Primera, Primera+CuadrosFinal):
        VinculaArchivo ('../../'+ImagenFinal, i)

#------------------------------------------------
# Crea un vínculo a un archivo con un número
def VinculaArchivo (Nombre, Numero):
    NumeroCadena = '%06d' % Numero
    Destino = Dir + '/' + DirFinal + '/' + NumeroCadena + '.png'
    os.symlink(Nombre, Destino)

#------------------------------------------------
# Dice al usuario cuáles pueden ser las órdenes para avconv
def InformaOrdenes():
    v = str(FPS)

    OrdenSinMP4 = ("avconv -f image2 -r " + v + " -i '" + Clave +
                   "/final/%06d.png' -r " + v +
                   " -vcodec libx264 " + Clave + ".mp4")

    OrdenSinOgv = ("avconv -f image2 -r " + v + " -i '" + Clave +
                   "/final/%06d.png' -r " + v +
                   " -q:v 10 " + Clave + ".ogv")

    OrdenConMP4 = ("avconv -f image2 -r " + v + " -i '" + Clave +
                "/final/%06d.png' -i audio.mp3 -r " + v + " -vcodec libx264 " +
                "-strict experimental " + Clave + ".mp4")

    print
    print 'Posible orden para generar el vídeo en MP4 sin audio:'
    print OrdenSinMP4
    print
    print 'Posible orden para generar el vídeo en ogv sin audio:'
    print OrdenSinOgv
    print
    print 'Posible orden para generar el vídeo en MP4 con audio:'
    print OrdenConMP4
    print

#------------------------------------------------
# Convierte una cantidad de tiempo a formato legible
def HumanizaTiempo(Tiempo):
    if Tiempo<60:
        ExpresionTiempo = str(Tiempo) + ' s'
    else:
        Minutos = Tiempo / 60
        Segundos = Tiempo % 60
        if Minutos<60:
            ExpresionTiempo = str(Minutos) + ' min ' + str(Segundos) + ' s'
        else:
            Horas = Minutos / 60
            Minutos = Minutos % 60
            ExpresionTiempo = (str(Horas) + ' h ' + str(Minutos) + ' min ' +
                               str(Segundos) + ' s')
    return ExpresionTiempo

#------------------------------------------------
# Funciones auxiliares
#------------------------------------------------

#------------------------------------------------
# Interpola entre dos valores
def Interpola (Inicio, Final, t):
    return str(int((1-t)*Inicio + t*Final))

#------------------------------------------------
# Convierte '/' y '.' en '-' en una cadena
def HigienizaNombre(Cadena):
    Cadena = Cadena.replace('/', '-')
    Cadena = Cadena.replace('.', '-')
    return Cadena

#------------------------------------------------
# El programa
#------------------------------------------------

print 'video.py - versión ' + Version

# Analizamos la línea de órdenes con la que nos han invocado
Invocacion = AnalizaInvocacion()

# Si no nos han pedido nada, mostramos la ayuda
if Invocacion['vacia']: MostrarAyuda()

else:
    # Vemos qué archivo nos pasan por la línea de órdenes
    if Invocacion['archivo']:
        Archivo = Invocacion['archivo']

        if GestionaNombreArchivo(Archivo):
            # No utilizo la petición de comprobación
            # porque siempre se hace la comprobación,
            # pero esto podría cambiar en un futuro
            SolicitadaComprobacion = Invocacion['comprobacion']

            # Leemos la descripción del archivo
            LeeArchivo(Archivo)

            # Comprobamos los datos
            CompruebaDatos()

            # Informamos de los datos
            if Invocacion['informacion']:
                InformaDatos()

            if Invocacion['generacion']:
                if TodoBien:
                    # Este es el trabajo duro
                    GeneracionCompleta()
                else:
                    print 'Hay errores, no se pueden generar las imágenes'

            if Invocacion['ordenes']:
                # Informamos de las órdenes para avconv
                InformaOrdenes()

    else: print 'Falta el nombre del archivo'

    print 'Terminado.'
