Flujos de Trabajos

Por defecto, los productos de multimedia de la fundación Cenditel no posee su propio flujo de trabajo al estilo de Plone.

cenditel.transcodedaemon

Este producto posee su propio flujo de trabajo que se basa en teoría de colas, usa la disciplina primero en entrar, primero en salir (FIFO). El flujo de trabajo, se encuentra estrechamente relacionado a los productos cenditel.audio y cenditel.video en los métodos de PlayingAudioType and PlayingVideoType de los módulos audioview.py y videoview.py de los paquetes browser.

De manera general, los métodos mencionados anteriormente obtienen una serie de configuraciones del panel de control de la aplicación los modifica parcialmente en caso de ser necesario eliminando los simbolos / sobrantes, realizando concatenaciones entre las configuraciones, URL del sitio, y la porción de URL que sera utilizada por los nuevos archivos codificados en formatos libres.

Origen de los datos

En el caso del producto cenditel.audio, el método PlayingAudioType que pertenece a la clase audioView del modulo audioview.py en el paquete browser, es el encargado de obtener los datos que serán manipulados por cenditel.transcodedaemon. A continuación se desglosa dicho método.

def PlayingAudioType(self):

        """
        Primero vamos  a obtener la configuración desde el panel de control:
        para eso, aplicamos la documentación de Plone.app.registry
        que puede ser vista en https://pypi.org/project/plone.app.registry

            >>> registry = getUtility(IRegistry)
            >>> settings = registry.forInterface(ITranscodeSetings)
            >>> self.SERVER = self.RemoveSlash(settings.adress_of_streaming_server)
            >>> VIDEO_PARAMETRES_TRANSCODE = settings.ffmpeg_parameters_video_line
            >>> AUDIO_PARAMETRES_TRANSCODE = settings.ffmpeg_parameters_audio_line
            >>> audio_content_types=settings.audio_valid_content_types
            >>> video_content_types=settings.video_valid_content_types

        De esta manera, hacemos un llamado a los distintos registros almacenados
        por el panel de configuración que nos serán útiles en este caso.
        Ahora, se declaran variables que son de interés para entender el contexto
        de la aplicación:

           >>> self.MyTitle = self.context.Title()
           >>> idvideo=self.context.getId()
           >>> url = self.context.absolute_url()
           >>> self.PathOfFile = MFNI.ReturnPathOfFile(url)
           >>>virtualobject=self.context.getVideo()
           >>>self.filenamesaved=virtualobject.filename
           >>> self.extension=MTDI.CheckExtension(self.filenamesaved)

        Para entender de mejor manera el código. Verifique la documentación de
        los métodos en el los script asociados a ellos.
        Las importaciones e instancias se encuentran en el modulo videoview.py.
        A continuación, se verifica si el archivo que fue cargado por el usuario
        tiene extensión ogg la cual corresponde a formatos de archivo basados en
        estándares libres.

            >>> if self.extension=="ogg":
                 ... self.folderfileOGG=self.PathOfFile+'/' + quote(self.filenamesaved)
                 ... self.prefiletranscoded=self.STORAGE+self.PathOfFile+'/'+self.filenamesaved

        En caso de cumplirse la condición, se crea una nueva variable llamada
        FolderFile que posee parte de la URL del servidor de streaming, y otra
        variable que por su nombre indica que el archivo ha sido precodificado
        por el usuario desde antes de ser cargado al servidor.
        A continuación se verifica si ese archivo existe, y en caso de ser verdad,
        revisa si no existe en la lista de archivos disponibles, de ser así
        se agrega a dicha lista, en caso contrario se completa la ejecución
        del método y por tanto el flujo del transcode ya que no fue necesario
        convertir el archivo y puede ser publicado inmediatamente.

            >>> if path.isfile(self.prefiletranscoded)==True:
                    ... self.StatusOfFile=ServiceList.available(idvideo,self.prefiletranscoded)
                    ... if self.StatusOfFile == False:
                    ....... ServiceList.AddReadyElement(idaudio,self.prefiletranscoded)
                    ....... self.StatusOfFile=True
                    ....... ServiceList.SaveInZODB()
                    ....... self.AbsoluteServerPath = self.SERVER + self.folderfileOGG
                    ... else:
                    ....... self.AbsoluteServerPath = self.SERVER + self.folderfileOGG

        En caso contrario a que la extensión del archivo subido por el usuario
        sea ogg, se dispara la ejecución de un método que se encargará de
        registrar el archivo en lista de espera y almacenar en cenditelmultimedia.fs
        datos que no son accesibles desde el panel de control que pueden ser
        usados por el convertidor de formatos. También se declaran otras
        variables que son utilizadas a nivel de vista de usuario para la
        presentación del contenido, para información acerca de los métodos
        revise la documentación de los mismos en el código correspondiente.

        >>> else:
            newtrans_init_(self.STORAGE,
                           self.PathOfFile,
                           self.filenamesaved,
                           idvideo,
                           VIDEO_PARAMETRES_TRANSCODE,
                           AUDIO_PARAMETRES_TRANSCODE,
                           audio_content_types,
                           video_content_types)
            self.folderfileOGG=MTDI.newname(self.PathOfFile+'/' + self.filenamesaved)
            self.AbsoluteServerPath = self.SERVER + MTDI.nginxpath(self.folderfileOGG)
            self.newfiletranscoded=MTDI.nginxpath(self.STORAGE+self.folderfileOGG)
            self.StatusOfFile = ServiceList.available(idvideo, self.newfiletranscoded)

        La ultima sección del método, verifica el valor de una variable bandera
        que revisa si el archivo se encuentra disponible. En caso contrario devuelve
        un error.
            >>> if self.StatusOfFile == True:
                ... self.newfilename=MTDI.newname(self.filenamesaved)
            ... else:
                ... self.newfilename=_('The file is not ready yet, please contact
                site administration')
        """

        registry = getUtility(IRegistry)
        settings = registry.forInterface(ITranscodeSetings)
        self.SERVER = self.RemoveSlash(settings.adress_of_streaming_server)
        VIDEO_PARAMETRES_TRANSCODE = settings.ffmpeg_parameters_video_line
        AUDIO_PARAMETRES_TRANSCODE = settings.ffmpeg_parameters_audio_line
        audio_content_types=settings.audio_valid_content_types
        video_content_types=settings.video_valid_content_types
        self.STORAGE = self.RemoveSlash(settings.mount_point_fss)
        self.MyTitle = self.context.Title()
        idvideo=self.context.getId()
        url = self.context.absolute_url()
        self.PathOfFile = MFNI.ReturnPathOfFile(url)
        virtualobject=self.context.getVideo()
        self.filenamesaved=virtualobject.filename
        self.extension=MTDI.CheckExtension(self.filenamesaved)
        if self.extension=="ogg":
            self.folderfileOGG=self.PathOfFile+'/' + quote(self.filenamesaved)
            self.prefiletranscoded=self.STORAGE+self.PathOfFile+'/'+self.filenamesaved
            if path.isfile(self.prefiletranscoded)==True:
                self.StatusOfFile=ServiceList.available(idvideo,self.prefiletranscoded)
                if self.StatusOfFile == False:
                    ServiceList.AddReadyElement(idaudio,self.prefiletranscoded)
                    ServiceList.SaveInZODB()
                    self.AbsoluteServerPath = self.SERVER + self.folderfileOGG
                else:
                    self.AbsoluteServerPath = self.SERVER + self.folderfileOGG
            else:
                print _("File not found "+self.prefiletranscoded)
                self.Error=True
                self.ErrorSituation()
        else:
            newtrans_init_(self.STORAGE,
                           self.PathOfFile,
                           self.filenamesaved,
                           idvideo,
                           VIDEO_PARAMETRES_TRANSCODE,
                           AUDIO_PARAMETRES_TRANSCODE,
                           audio_content_types,
                           video_content_types)
            self.folderfileOGG=MTDI.newname(self.PathOfFile+'/' + self.filenamesaved)
            self.AbsoluteServerPath = self.SERVER + MTDI.nginxpath(self.folderfileOGG)
            self.newfiletranscoded=MTDI.nginxpath(self.STORAGE+self.folderfileOGG)
            self.StatusOfFile = ServiceList.available(idvideo, self.newfiletranscoded)
            if self.StatusOfFile == True:
                self.newfilename=MTDI.newname(self.filenamesaved)
            else:
                self.newfilename=_('The file is not ready yet, please contact site
                administration')
        return

Registro en espera

Como se mencionó en la sección anterior, cuando un archivo subido no corresponde a un archivo ogg dicho archivo es registrado en espera para posteriormente ser codificado según la posición en la cola. En otras palabras, imagine la cola de un banco, donde usted entra y espera su turno, luego es atendido, y posteriormente sale del banco. El sistema de conversión funciona de igual manera.

Ahora se va a analizar, el método newtrans_init_ que es el encargado de registrar los elementos en la lista de espera.

"""
Como se puede observar a continuación el método recibe los siguientes parámetros:

* STORAGE: Es la URL al directorio raíz del elemento.
* path: Corresponde a la URL del elemento y es donde se guarda el archivo
original en el disco duro del servidor.
* filenamesaved: Nombre del archivo guardado originalmente.
* idfile: Identificador del Elemento en el sitio Plone
* VIDEO_PARAMETRES_TRANSCODE: Los datos de configuración del elemento vídeo
en el panel de control.
* AUDIO_PARAMETRES_TRANSCODE: Los datos de configuración del elemento vídeo
el panel de control.
* audio_content_types: Corresponde a los mimetypes para archivos de audio
validos en el panel de control.
* video_content_types: Corresponde a los mimetypes para archivos de vídeo
validos en el panel de control.
La primera linea, crea una variable que concatena los valores especificados
anteriormente luego, esta es modificada por un método
para entender el funcionamiento de este, vea la documentación respectiva en
el código fuente del respectivo paquete.
>>> PathToOriginalFile = STORAGE + path +'/'+ filenamesaved
>>> newfolderfile=MTD.nginxpath(PathToOriginalFile)

Las siguientes lineas, verifican si el valor de las variables en el panel de
control corresponden a las variables almacenadas en la Base de Datos orientada
a objetos del producto, y en caso de no ser de esa manera, cambian el valor almacenado.

La siguiente linea, verifica si el archivo no esta en la lista de espera
(resultado del método uploaded) y no se encuentra tampoco en la lista de archivos
disponibles (resultado del método available) y no se encuentra siendo codificado en el momento
(resultado del método transcoding)y en el caso de ser negativas todas las condiciones
se el archivo se registrá en la lista de espera.

>>> if ServiceList.uploaded(idfile, PathToOriginalFile)== False and\
ServiceList.available(idfile, newfolderfile)== False\
and ServiceList.transcoding(PathToOriginalFile)== False:
    ... ServiceList.RegisterWaitingFile(idfile, PathToOriginalFile)

La ultima seccion de codigo de este metodo, se encarga de disparar un sub
proceso basado en programación multihilos que se ejecutará siempre que no se
este convirtiendo ningun archivo en el momento.

>>> import threading
>>> if ServiceList.CurrentTranscoding()=="":
    ... class MyThread(threading.Thread):
    ... def run(self):
           transcodedaemon()
           MyThread().start()
>>> return
"""
def newtrans_init_(STORAGE, path, filenamesaved,\
                   idfile, VIDEO_PARAMETRES_TRANSCODE,\
                   AUDIO_PARAMETRES_TRANSCODE,\
                   audio_content_types, video_content_types):
        PathToOriginalFile = STORAGE + path +'/'+ filenamesaved
        newfolderfile=MTD.nginxpath(PathToOriginalFile)
        if ServiceList.CheckItemZODB('waiting')==False:
                ServiceList.AddObjectZODB('waiting',[])
        if ServiceList.CheckItemZODB('current')==False:
                ServiceList.AddObjectZODB('current','')
                ServiceList.SaveInZODB()
        if ServiceList.CheckItemZODB('ready')==False:
                ServiceList.AddObjectZODB('ready',[])
                ServiceList.SaveInZODB()
        if ServiceList.CheckItemZODB('Video_Parameters')==False:
                ServiceList.AddObjectZODB('Video_Parameters', VIDEO_PARAMETRES_TRANSCODE)
                ServiceList.SaveInZODB()
        if ServiceList.CheckItemZODB('Audio_Parameters')==False:
                ServiceList.AddObjectZODB('Audio_Parameters', AUDIO_PARAMETRES_TRANSCODE)
                ServiceList.SaveInZODB()

        if ServiceList.CheckItemZODB('Video_ContentTypes')==False:
                ServiceList.AddObjectZODB('Video_ContentTypes', video_content_types)
                ServiceList.SaveInZODB()
        if ServiceList.CheckItemZODB('Audio_ContentTypes')==False:
                ServiceList.AddObjectZODB('Audio_ContentTypes', audio_content_types)
                ServiceList.SaveInZODB()

        if ServiceList.root['Audio_Parameters']!=AUDIO_PARAMETRES_TRANSCODE:
                ServiceList.root['Audio_Parameters']=AUDIO_PARAMETRES_TRANSCODE
                ServiceList.SaveInZODB()
        if ServiceList.root['Video_Parameters']!=VIDEO_PARAMETRES_TRANSCODE:
                ServiceList.root['Video_Parameters']=VIDEO_PARAMETRES_TRANSCODE
                ServiceList.SaveInZODB()

        if ServiceList.root['Video_ContentTypes']!=video_content_types:
                ServiceList.root['Video_ContentTypes']=video_content_types
                ServiceList.SaveInZODB()

        if ServiceList.root['Audio_ContentTypes']!=audio_content_types:
                ServiceList.root['Audio_ContentTypes']=audio_content_types
                ServiceList.SaveInZODB()

        if ServiceList.uploaded(idfile, PathToOriginalFile)== False and \
        ServiceList.available(idfile, newfolderfile)== False and \
        ServiceList.transcoding(PathToOriginalFile)== False:
                ServiceList.RegisterWaitingFile(idfile, PathToOriginalFile)
        import threading
        if ServiceList.CurrentTranscoding()=="":
                class MyThread(threading.Thread):
                        def run(self):
                                transcodedaemon()
                MyThread().start()
        return

Prestación del servicio

Una vez que el archivo cliente ha sido registrado en la lista de espera, y no existe ningún elemento siendo codificado en el momento, se dispara el siguiente método que es el encargado de extraer los archivos en espera y pasar la URL en el disco duro a una instancia de ffmpeg que se encargará de crear un archivo de salida con un nuevo formato libre que permita su utilización usando html5.

def transcodedaemon():
    """

    La primera linea, obtiene la cantidad de registros en espera.
    Si la cantidad es mayor a 0.

    >>> while(ServiceList.FileWaitings()>0):

    Las siguientes lineas extraen la dirección del primer archivo

        ... element=ServiceList.WaitingElement()
        ... listpath=element.values()
        ... PathToOriginalFile=listpath[0]

    Por otro lado, reseteando la variable se extrae el identificador del archivo.

        ... listpath=''
        ... listpath=element.keys()
        ... idfile=listpath[0]

    A continuación se verifica si el archivo existe, de ser así se elimina el elemento
    de la lista de espera, se agrega la url del a una variable que controla
    el flujo en el codificador se dispara el método ``transcode`` que recibe
    una serie de parámetros que serán explicados a continuación.

    >>> if os.path.isfile(PathToOriginalFile):

                ... ServiceList.DeleteElement(idfile, PathToOriginalFile)
                ... ServiceList.AddActiveTranscoding(PathToOriginalFile)
                ... PathToTranscodedFile = MTD.transcode(PathToOriginalFile,
                                                 ServiceList.root['Video_Parameters'],
                                                 ServiceList.root['Audio_Parameters'],
                                                 ServiceList.root['Video_ContentTypes'],
                                                 ServiceList.root['Audio_ContentTypes'],)

    * ServiceList.root['Video_Parameters']: Parámetros de codificación de archivos
        de vídeo.
    * ServiceList.root['Audio_Parameters']: Parámetros de codificación de archivos
        de audio.
    * ServiceList.root['Video_ContentTypes']: Tipos de contenido de vídeo validos.
    * ServiceList.root['Audio_ContentTypes']: Tipos de contenido de audio validos.

   Luego, es eliminado el elemento de la variable de control de codificación y luego
   es pasada a una lista de elementos guardados y se guarda la información en
   la base de datos del producto.

    >>> ServiceList.RemoveActiveTranscoding()
    >>> ServiceList.AddReadyElement(idfile, PathToTranscodedFile)
    >>> ServiceList.SaveInZODB()

    En caso de que la condición de existencia del archivo no se cumpla,
    Se elimina el elemento de la lista de espera y se manda un log de elemento
    no encontrado.

    ... else:
        ... ServiceList.DeleteElement(idfile, PathToOriginalFile)
        ... print "NOT FOUND "+ PathToOriginalFile
        ... ServiceList.SaveInZODB()
    """
    while(ServiceList.FileWaitings()>0):
            element=ServiceList.WaitingElement()
            listpath=element.values()
            PathToOriginalFile=listpath[0]
            listpath=''
            listpath=element.keys()
            idfile=listpath[0]
            if os.path.isfile(PathToOriginalFile):
                    ServiceList.DeleteElement(idfile, PathToOriginalFile)
                    ServiceList.AddActiveTranscoding(PathToOriginalFile)
                    PathToTranscodedFile = MTD.transcode(PathToOriginalFile,
                                                 ServiceList.root['Video_Parameters'],
                                                 ServiceList.root['Audio_Parameters'],
                                                 ServiceList.root['Video_ContentTypes'],
                                                 ServiceList.root['Audio_ContentTypes'],)
                    ServiceList.RemoveActiveTranscoding()
                    ServiceList.AddReadyElement(idfile, PathToTranscodedFile)
                    ServiceList.SaveInZODB()
            else:
                    ServiceList.DeleteElement(idfile, PathToOriginalFile)
                    print "NOT FOUND "+ PathToOriginalFile
                    ServiceList.SaveInZODB()
    print "Daemon is waiting for File"
    return

Una vez finalizado los procesos, el archivo queda disponible para streaming. Ya sea en el caso de los archivos de audio o de los archivos de vídeo.