Archivo

Entradas Etiquetadas ‘directorio’

Listar archivos dentro de un directorio o carpeta en C

Martes, 27 de Septiembre de 2011 Gaspar Fernández 4 comentarios

Árbol de directoriosNuestro software debe buscar archivos dentro de un directorio determinado, ya sea un archivo especial, una recopilación de datos del disco duro, una búsqueda de plugins, etc…

Hemos elegido lenguaje C para hacer esto, porque en bash podemos llamar a ls o find y apaga y vámonos.

Estas funciones, nos recordarán al uso de archivos con fopen() y fclose().

Para usar esta implementación, como veremos en el ejemplo, debemos incluir <sys/types.c> y <dirent.h> y, en principio, empezaremos con un ejemplo sencillo que lista los archivos del directorio actual (una vez tenemos el nombre y la ruta del archivo podemos hacer con él lo que queramos):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>

/* Función para devolver un error en caso de que ocurra */
void error(const char *s);
/* Función que hace algo con un archivo */
void procesoArchivo(char *archivo);

int main()
{
  /* Con un puntero a DIR abriremos el directorio */
  DIR *dir;
  /* en *ent habrá información sobre el archivo que se está "sacando" a cada momento */
  struct dirent *ent;

  /* Empezaremos a leer en el directorio actual */
  dir = opendir (".");

  /* Miramos que no haya error */
  if (dir == NULL)
    error("No puedo abrir el directorio");
 
  /* Una vez nos aseguramos de que no hay error, ¡vamos a jugar! */
  /* Leyendo uno a uno todos los archivos que hay */
  while ((ent = readdir (dir)) != NULL)
    {
      /* Nos devolverá el directorio actual (.) y el anterior (..), como hace ls */
      if ( (strcmp(ent->d_name, ".")!=0) && (strcmp(ent->d_name, "..")!=0) )
    {
      /* Una vez tenemos el archivo, lo pasamos a una función para procesarlo. */
      procesoArchivo(ent->d_name);
    }
    }
  closedir (dir);

  return EXIT_SUCCESS;
}

void error(const char *s)
{
  /* perror() devuelve la cadena S y el error (en cadena de caracteres) que tenga errno */
  perror (s);
  exit(EXIT_FAILURE);
}

void procesoArchivo(char *archivo)
{
  /* Para "procesar", o al menos, hacer algo con el archivo, vamos a decir su tamaño en bytes */
  /* para ello haremos lo que vemos aquí: http://totaki.com/poesiabinaria/2010/04/tamano-de-un-fichero-en-c/ */
  FILE *fich;
  long ftam;

  fich=fopen(archivo, "r");
  if (fich)
    {
      fseek(fich, 0L, SEEK_END);
      ftam=ftell(fich);
      fclose(fich);
      /* Si todo va bien, decimos el tamaño */
      printf ("%30s (%ld bytes)\n", archivo, ftam);
    }
  else
    /* Si ha pasado algo, sólo decimos el nombre */
    printf ("%30s (No info.)\n", archivo);
}

Este es un ejemplo sencillo, podemos profundizar un poco más. Vemos que readdir() nos devuelve todo lo que hay, y a veces, es muy útil diferenciar entre un fichero, un directorio, un enlace, un socket, un fifo, etc… para eso, en un principio utilizaremos la información del mismo dirent, que a veces la encontramos de forma rápida, aunque existen sistemas de archivos que no nos facilitan esa información de forma tan inmediata y tenemos que hacer stat(), un ejemplo de ello es nfs, ya que es significativamente más lento obtener esa información, si no la necesitamos, no intenta obtenerla, es para esos casos cuando obligamos a su obtención con stat().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>

/* Función para devolver un error en caso de que ocurra */
void error(const char *s);

/* Calculamos el tamaño del archivo */
long fileSize(char *fname);

/* Sacamos el tipo de archivo haciendo un stat(), es como el stat de la línea de comandos */
unsigned char statFileType(char *fname);

/* Función que hace algo con un archivo, pero le pasamos el dirent completo, usaremos más datos */
void procesoArchivo(char *ruta, struct dirent *ent);

int main(int argc, char *argv[])
{
  /* Con un puntero a DIR abriremos el directorio */
  DIR *dir;
  /* en *ent habrá información sobre el archivo que se está "sacando" a cada momento */
  struct dirent *ent;

  if (argc != 2)
    {
      error("Uso: ./directorio_2 <ruta>\n");
    }
  /* Empezaremos a leer en el directorio actual */
  dir = opendir (argv[1]);

  /* Miramos que no haya error */
  if (dir == NULL)
    error("No puedo abrir el directorio");
 
  /* Una vez nos aseguramos de que no hay error, ¡vamos a jugar! */
  /* Leyendo uno a uno todos los archivos que hay */
  while ((ent = readdir (dir)) != NULL)
    {
      /* Nos devolverá el directorio actual (.) y el anterior (..), como hace ls */
      if ( (strcmp(ent->d_name, ".")!=0) && (strcmp(ent->d_name, "..")!=0) )
    {
      /* Una vez tenemos el archivo, lo pasamos a una función para procesarlo. */
      procesoArchivo(argv[1], ent);
    }
    }
  closedir (dir);

  return EXIT_SUCCESS;
}

void error(const char *s)
{
  /* perror() devuelve la cadena S y el error (en cadena de caracteres) que tenga errno */
  perror (s);
  exit(EXIT_FAILURE);
}

long fileSize(char *fname)
{
  FILE *fich;
  long ftam=-1;

  fich=fopen(fname, "r");
  if (fich)
    {
      fseek(fich, 0L, SEEK_END);
      ftam=ftell(fich);
      fclose(fich);
    }
  else
    printf("ERRNO: %d - %s\n", errno, strerror(errno));
  return ftam;
}

void procesoArchivo(char *ruta, struct dirent *ent)
{
  long ftam;
  char *nombrecompleto;
  char strtam[20];
  char strtipo[30]="";
  /* Tiene que ser del mismo tipo de dirent.d_type en nuestro sistema */
  static unsigned char tipoID[7]={DT_BLK, DT_CHR, DT_DIR, DT_FIFO, DT_LNK, DT_REG, DT_SOCK};
  static char* tipoSTRs[7]={"Dispositivo de bloques", "Dispositivo de caracteres", "Directorio", "FIFO", "Enlace", "Archivo regular", "Socket Unix"};

  int i;
  int tmp;
  unsigned char tipo;

  /* Sacamos el nombre completo con la ruta del archivo */
  tmp=strlen(ruta);
  nombrecompleto=malloc(tmp+strlen(ent->d_name)+2); /* Sumamos 2, por el \0 y la barra de directorios (/) no sabemos si falta */
  if (ruta[tmp-1]=='/')
    sprintf(nombrecompleto,"%s%s", ruta, ent->d_name);
  else
    sprintf(nombrecompleto,"%s/%s", ruta, ent->d_name);

  /* Calcula el tamaño */
  ftam=fileSize(nombrecompleto);
  if (ftam>=0)
    sprintf(strtam, "%ld bytes", ftam);
  else
    strcpy(strtam, "No info");

  /* A veces ent->d_type no nos dice nada, eso depende del sistema de archivos que estemos */
  /* mirando, por ejemplo ext*, brtfs, sí nos dan esta información. Por el contrario, nfs */
  /* no nos la da (directamente, una vez que hacemos stat sí lo hace), y es en estos casos donde probamos con stat() */
  tipo=ent->d_type;
  if (tipo==DT_UNKNOWN)
    tipo=statFileType(nombrecompleto);

  if (tipo!=DT_UNKNOWN)
    {
      /* Podíamos haber hecho un switch con los tipos y devolver la cadena,
         pero me da la impresión de que así es menos costoso de escribir. */

      i=0;
      while ( (i<7) && (tipo!=tipoID[i]) )
    ++i;

      if (i<7)
    strcpy(strtipo, tipoSTRs[i]);
    }

  /* Si no hemos encontrado el tipo, éste será desconocido */
  if (strtipo[0]=='\0')
    strcpy(strtipo, "Tipo desconocido");

  printf ("%30s (%s)\t%s \n", ent->d_name, strtam, strtipo);

  free(nombrecompleto);
}

/* stat() vale para mucho más, pero sólo queremos el tipo ahora */
unsigned char statFileType(char *fname)
{
  struct stat sdata;

  /* Intentamos el stat() si no funciona, devolvemos tipo desconocido */
  if (stat(fname, &sdata)==-1)
    {
      return DT_UNKNOWN;
    }


  switch (sdata.st_mode & S_IFMT)
    {
    case S_IFBLK:  return DT_BLK;
    case S_IFCHR:  return DT_CHR;
    case S_IFDIR:  return DT_DIR;
    case S_IFIFO:  return DT_FIFO;
    case S_IFLNK:  return DT_LNK;
    case S_IFREG:  return DT_REG;
    case S_IFSOCK: return DT_SOCK;
    default:       return DT_UNKNOWN;
    }
}

Un ejemplo más grande, contamos los archivos que hay en un árbol de directorios. Uno a uno, pero de forma recursiva (cuando encontramos un directorio nos meteremos dentro y llamaremos de nuevo a la función principal (cuentaArchivos()):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>

/* Función para devolver un error en caso de que ocurra */
void error(const char *s);

/* Calculamos el tamaño del archivo */
long fileSize(char *fname);

/* Sacamos el tipo de archivo haciendo un stat(), es como el stat de la línea de comandos */
unsigned char statFileType(char *fname);

/* Intenta sacar el tipo de archivo del ent */
unsigned char getFileType(char *ruta, struct dirent *ent);

/* Obtiene el nombre del fichero con la ruta completa */
char *getFullName(char *ruta, struct dirent *ent);

/* Genera una cadena de espacios, para dibujar el árbol */
char *generaPosStr(int niv);

/* Función principal, que cuenta archivos */
unsigned cuentaArchivos(char *ruta, int niv);

int main(int argc, char *argv[])
{
  unsigned num;

  if (argc != 2)
    {
      error("Uso: ./directorio_2 <ruta>\n");
    }
  printf("Entrando en: %s\n", argv[1]);
  num=cuentaArchivos(argv[1], 1);
  printf("%s . Total: %u archivos\n", argv[1], num);
  /* Empezaremos a leer en el directorio actual */

  return EXIT_SUCCESS;
}

void error(const char *s)
{
  /* perror() devuelve la cadena S y el error (en cadena de caracteres) que tenga errno */
  perror (s);
  exit(EXIT_FAILURE);
}

char *getFullName(char *ruta, struct dirent *ent)
{
  char *nombrecompleto;
  int tmp;

  tmp=strlen(ruta);
  nombrecompleto=malloc(tmp+strlen(ent->d_name)+2); /* Sumamos 2, por el \0 y la barra de directorios (/) no sabemos si falta */
  if (ruta[tmp-1]=='/')
    sprintf(nombrecompleto,"%s%s", ruta, ent->d_name);
  else
    sprintf(nombrecompleto,"%s/%s", ruta, ent->d_name);
 
  return nombrecompleto;
}

char *generaPosStr(int niv)
{
  int i;
  char *tmp=malloc(niv*2+1);    /* Dos espacios por nivel más terminador */
  for (i=0; i<niv*2; ++i)
    tmp[i]=' ';
  tmp[niv*2]='\0';
  return tmp;
}

unsigned cuentaArchivos(char *ruta, int niv)
{
  /* Con un puntero a DIR abriremos el directorio */
  DIR *dir;
  /* en *ent habrá información sobre el archivo que se está "sacando" a cada momento */
  struct dirent *ent;
  unsigned numfiles=0;          /* Ficheros en el directorio actual */
  unsigned char tipo;       /* Tipo: fichero /directorio/enlace/etc */
  char *nombrecompleto;     /* Nombre completo del fichero */
  char *posstr;         /* Cadena usada para posicionarnos horizontalmente */
  dir = opendir (ruta);

  /* Miramos que no haya error */
  if (dir == NULL)
    error("No puedo abrir el directorio");
 
  while ((ent = readdir (dir)) != NULL)
    {
      if ( (strcmp(ent->d_name, ".")!=0) && (strcmp(ent->d_name, "..")!=0) )
    {
      nombrecompleto=getFullName(ruta, ent);
      tipo=getFileType(nombrecompleto, ent);
      if (tipo==DT_REG)
        {
          ++numfiles;
        }
      else if (tipo==DT_DIR)
        {
          posstr=generaPosStr(niv);
          printf("%sEntrando en: %s\n", posstr, nombrecompleto);         
          printf("%s%s . Total: %u archivos ", posstr, nombrecompleto, cuentaArchivos(nombrecompleto, niv+1));
          /* Podemos poner las líneas que queramos */
          printf("\n");
          free(posstr);
        }
      free(nombrecompleto);
    }
    }
  closedir (dir);
 
  return numfiles;
}

unsigned char getFileType(char *nombre, struct dirent *ent)
{
  unsigned char tipo;

  tipo=ent->d_type;
  if (tipo==DT_UNKNOWN)
    {
      tipo=statFileType(nombre);
    }

  return tipo;
}

/* stat() vale para mucho más, pero sólo queremos el tipo ahora */
unsigned char statFileType(char *fname)
{
  struct stat sdata;

  /* Intentamos el stat() si no funciona, devolvemos tipo desconocido */
  if (stat(fname, &sdata)==-1)
    {
      return DT_UNKNOWN;
    }

  switch (sdata.st_mode & S_IFMT)
    {
    case S_IFBLK:  return DT_BLK;
    case S_IFCHR:  return DT_CHR;
    case S_IFDIR:  return DT_DIR;
    case S_IFIFO:  return DT_FIFO;
    case S_IFLNK:  return DT_LNK;
    case S_IFREG:  return DT_REG;
    case S_IFSOCK: return DT_SOCK;
    default:       return DT_UNKNOWN;
    }
}

Se puede descargar todo el código fuente desde aquí: Listado de archivos dentro de un directorio en C (3.5Kb)

Actualización (14/10/2011): En el blog código para llevar encontramos un ejemplo de cómo utilizar este código para encontrar el número de núcleos de nuestra CPU.

Now playing… para mplayer

Domingo, 14 de Noviembre de 2010 Gaspar Fernández Sin comentarios

screenshot-14-11-2010-201150Se trata de un script para bash que representará en pantalla información del archivo que se está reproduciendo al mismo tiempo que este se visualiza. Perfecto para un sistema mediacenter.

Requerimientos:

  • bash >=3.0
  • mplayer
  • xosd
  • utilidades de sistema utilizadas: date, sed, tr, cut, sleep

Entre sus características destaca la escritura de un archivo: $HOME/.videolog con los archivos que se reproducen. Uno de los posibles usos es la descarga de muchos vídeos de youtube en un directorio concreto, posterior visualización y borrado de cada uno de los archivos tras su visionado.

Además, si el archivo de audio o vídeo a reproducir contiene metadatos de Artista y Título, se visualizará la información de estos metadatos siempre que no estén en la lista negra (variable $ARTISTAS_PROHIBIDOS), en otro caso visualizaremos el nombre del archivo arreglado (eliminando las cadenas que aparecen en la variable FILENAME_RECORTADO, aquí podemos incluir extensiones o pequeñas anotaciones sobre la calidad o resolución de un vídeo).

Se recomienda modificar XOSD_OPTIONS_ARTISTA, XOSD_OPTIONS_TITULO, XOSD_OPTIONS_INFO y XOSD_OPTIONS_DURACION de acuerdo a los tipos de letra, colores y todo lo que se desee. Dejo una pequeña referencia de las opciones de osd_cat:

–pos Posición del texto: top(arriba), middle(en medio de la pantalla), bottom(abajo)
-A Alineación del texto: left(izquierda), center(centrado), right(derecha)
-c Color: puede ser en notación html #rrggbb o por su nombre
-s Offset o desplazamiento de la sombra
-o Offset o desplazamiento desde la parte superior o inferior de la pantalla
-d Tiempo en segundos que se mantendrá el texto en la pantalla
-f Tipo de letra. Para ver los tipos de letra disponibles ejecutar xfontsel.

Inicio del archivo: mplayernp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#!/bin/bash

##########################################################################
#
# MPLAYER NOW PLAYING version 0.1
# Copyright (C) 2010 by Gaspar Fernández <gaspy at totaki.com>
# http://totaki.com/poesiabinaria
#
##########################################################################

#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License.

#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.

#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.

XOSD_OPTIONS_ARTISTA="--pos=bottom -A left -c #fff -o -110 -s 2 -d 10 -f -*-utopia-*-*-*-*-*-520-*-*-*-*-*-*"
XOSD_OPTIONS_TITULO="--pos=bottom -A left -c #fff -o -100 -s 2 -d 10 -f -*-utopia-*-*-*-*-*-420-*-*-*-*-*-*"
XOSD_OPTIONS_INFO="--pos=bottom -A left -c #fff -s 2 -o -80 -d 10 -f -*-utopia-*-*-*-*-*-320-*-*-*-*-*-*"
XOSD_OPTIONS_DURACION="--pos=bottom -A left -c #fff -s 2 -o -60 -d 10 -f -*-utopia-*-*-*-*-*-220-*-*-*-*-*-*"

# Si precalculamos esto, damos velocidad
ESCAPE=`echo -e "\e"`

ARTISTAS_PROHIBIDOS=("virtualdub")
FILENAME_RECORTADO=(".avi" ".flv" ".mp3" ".mp4")

function print_info()
{
    artist="$1"
    title="$2"
    filename="$3"
    width="$4"
    height="$5"
    fps="$6"
    minutos="${7/./,}"      # Si nuestra locale es es_ES debemos sustituir puntos por comas
    segundos="${8/./,}"
    if [[ -n $artist ]]
    then
    artist_text=${info_val[$artist]}
    for proh in ${ARTISTAS_PROHIBIDOS[*]}
    do
        if [[ `echo $artist_text | tr [:upper:] [:lower:]` =~ $proh ]]
        then
        artist="";
        title="";
        break;
        fi
    done
    fi

    if [[ -n $artist ]]
    then
    echo $artist_text | osd_cat $XOSD_OPTIONS_ARTISTA &
    fi
    if [[ -n $title ]]
    then
    title_text=${info_val[$title]}
    echo $title_text | osd_cat $XOSD_OPTIONS_TITULO &
    else
    # Si no tenemos la codificación correcta configurada, esto puede saltar
#   fname=`basename "$filename"`
#   dname=`dirname "$filename"`
#   defname=`ls "$dname/$fname"*`
#   defname=`basename "$defname"`
    defname=`basename "$filename"`
    for recorte in ${FILENAME_RECORTADO[*]}
    do
        defname=${defname/$recorte/}
    done
    echo $defname | osd_cat $XOSD_OPTIONS_TITULO &
    fi
    if [[ -n $fps ]]
    then
    echo $width"x"$height"@"$fps | osd_cat $XOSD_OPTIONS_INFO &
    fi
    duracion=`printf "%1.0f:%1.0f" "$minutos" "$segundos"`
    echo $duracion | osd_cat $XOSD_OPTIONS_DURACION &

    echo "`date +"%d/%m/%Y %H:%M"` Media: $filename Dur: $duracion" >> $HOME/.videolog

}

function keyvalue()
{
    key=`echo "$1" | cut -d= -f1`
    value=`echo "$1" | cut -d= -f2`

    res[0]=$key;
    if [[ -n $value ]]
    then
    res[1]=$value;
    else
    res[1]="0";
    fi

    if [[ -n `echo $key | grep "ID_CLIP_INFO_NAME\|ID_CLIP_INFO_VALUE"` ]]
    then
    # Si tenemos un ID_CLIP_INFO_NAMEx o ID_CLIP_INFO_VALUEx extraemos el x
    info=(`echo $key | sed -n -e "s/\([a-zA-Z_]*\)\([0-9]*\)/\1\n\2/p"`)
   
    res[0]=${info[0]}
    res[2]=${info[1]}
    fi
    echo -e ${res[0]}"\n"${res[1]}"\n"${res[2]}"\n"


}

function tituladora()
{

artist="";
title="";
while read -n 200 dato 
do
    if [[ -n `echo $dato | grep ID_` ]] # Extraemos información de mplayer
    then
    OLD_IFS=$IFS;
    IFS="
"
;
    kval=( `keyvalue $dato` );
#   echo ${kval[0]} "********"
    IFS=$OLD_IFS

    case ${kval[0]} in
        "ID_FILENAME")
        filename="${kval[1]}"          
        ;;
        "ID_VIDEO_WIDTH")
        width=${kval[1]}
        ;;
        "ID_VIDEO_HEIGHT")
        height=${kval[1]}
        ;;
        "ID_VIDEO_FPS")
        fps=${kval[1]}
        ;;
        "ID_LENGTH")
        seconds=${kval[1]};
        # Extrae la duración en minutos y segundos
        duration=( `echo "scale=0; $seconds / 60; scale=0; $seconds % 60" | bc` )
        ;;
        "ID_CLIP_INFO_NAME")
        if [[ `echo ${kval[1]} | tr [:upper:] [:lower:]` == "artist" ]]
            then
#           echo "ARTISTA CAPTURADO "${kval[2]};
            artist=${kval[2]} # Donde encontramos el artista
        else
            if [[ `echo ${kval[1]} | tr [:upper:] [:lower:]` == "title" ]]
            then
            title=${kval[2]} # Donde encontramos el artista
#           echo "TITULO CAPTURADO"
            fi
        fi
        ;;
        "ID_CLIP_INFO_VALUE")
        # Almacenamos el valor obtenido
#       echo "ALMACENO EN "${kval[2]} "ESTO" ${kval[1]} "**" ${kval[0]} ${kval[2]} = ${kval[1]}
        info_val[${kval[2]}]="${kval[1]}"
        ;;
    esac
#   echo ${info_val[@]}
    else
    # Si encontramos caracteres de control....
    if [[ $dato =~ $ESCAPE ]]
    then

        # Tal vez podamos comentar esto para dar velocidad --- INICIO
        if [[ $dato =~ "Position" ]]
        then       
        printf "%s\n" "$dato"
        else
        printf "%s" "$dato"
        fi
        # Tal vez podamos comentar esto para dar velocidad --- INICIO
    else
        if [[ ${dato:0:3} == "AO:" ]] # Aquí empieza el Audio... saltaremos tanto para archivos de audio como
                                      # para vídeos / películas... que también tengan audio
        then
        # Empieza la reproducción
        sleep 1 && print_info "$artist" "$title" "$filename" "$width" "$height" "$fps" "${duration[0]}" "${duration[1]}"
        fi

        echo $dato;
    fi
    fi
done
}
mplayer -identify $@ | tituladora

Por último, para utilizar este programa, podemos crear un alias a mplayer:

alias mplayer=”mplayernp”

Para finalizar dejo algúnos ejemplo de uso (son válidos también para mplayer, ya que mplayernp pasa los comandos tal cual a mplayer:

1
2
# Para reproducir archivos dentro de un directorio con orden aleatorio y en pantalla completa
mplayernp -fs `ls /directorio/*.avi | sort -R`

Vídeo de la foto: El Hombre de Negro - Greenfield

Vídeo digital: Secuencias de imágenes

Jueves, 16 de Septiembre de 2010 Gaspar Fernández Sin comentarios

Esta vez voy a presentar algunos ejemplos de uso de ffmpeg con secuencias de imágenes. Para empezar a trabajar con vídeo digital desde GNU/Linux.

Crear vídeo a partir de una secuencia de imágenes

Es una buena técnica el hecho de trabajar con secuencias de imágenes para crear un vídeo. Podemos, por ejemplo utilizar la potencia de Imagemagick (I, II, III, IV) para modificar el color de la imagen, etiquetarlas o darle algún tratamiento. O incluso para manejar transparencias en vídeo de una forma controlada. Para las secuencias de imágenes es conveniente utilizar un formato de imagen sin pérdidas como png, pnm, o jpg sin pérdidas (ojo, no jpg normal).

Para hacer la conversión podemos utilizar esta orden:

$ ffmpeg -f image2 -i directorio/imag%d.png -r 25 -vcodec mpeg4 video.avi

Elegimos como entrada un formato de imágenes y las exportaremos a 25 fps, con el codec mpeg4 y el archivo se llamará video.avi. Todas las imágenes estarán dentro de directorio y se llamarán imag0.png, imag1.png, imag2.png, …

Siempre depende el codec que vayamos a utilizar, si queremos enviarlo por Internet, o grabarlo en algún soporte como montaje definitivo podemos utilizar el formato mpeg4, theora, h264, vp8, etc; si queremos grabarlo en DVD podemos escoger mpeg2, aunque si vamos a utilizarlo como parte de nuestro proceso de montaje podemos usar dv o algún codec sin pérdidas como ljpeg, jpegls, v210, etc; el objetivo, para montaje es utilizar un formato sin comprimir o comprimido sin pérdidas.

Creando un vídeo sólo con una imagen

En ocasiones, puede ser necesario crear un archivo de vídeo que sólo contenga una imagen. ¿ Por qué ? Podemos crear algún script que haga algún efecto con ese vídeo, una transición, o podemos unirlo a un vídeo, para hacer de cortinilla de inicio o cierre.

Para ello hacemos lo siguiente:

$ ffmpeg -loop_input -f image2 -i imagen.png -vframes 50 -r 10 -vcodec libtheora video.avi

Donde grabaremos 50 frames con la imagen imagen.png a 10 fotogramas por segundo y con el codec theora. Aunque, en lugar de especificar los fps podemos especificar el tiempo que queremos que dure (en hh:mm:ss.[xxx], es decir, horas, minutos, seguntos, centésimas):

$ ffmpeg -loop_input -f image2 -i imagen.png -t 00:00:5.50 -r 10 -vcodec libtheora video.avi

Donde grabaremos un vídeo de 5 segundos y medio con la misma imagen.

Pasando un vídeo a secuencia de imágenes

Ahora tratamos de hacer la primera conversión, de vídeo a imágenes, intentando escoger el codec adecuado. Aunque podemos utilizar jpg, vamos a exportar a png para dar más calidad a las imágenes:

$ ffmpeg -i video.avi -f image2 -vcodec png video/frame_%d.png

Grabaremos los distintos fotogramas del video (video.avi) en el directorio video y con archivos llamados frame_1.png, frame_2.png, frame_3.png…

Jugando con ImageMagick (IV): Automatizando procesos, creando animaciones

Martes, 14 de Septiembre de 2010 Gaspar Fernández 3 comentarios

El cuarto post de la serie, anteriormente hablábamos de:

  1. Dimensiones, Captura, Color y Efectos
  2. Color (continuación) y Rotación
  3. Jugando con ImageMagick (III): Colecciones, texto, y unión de efectos

Ya que estamos hartos de ver lo que es capaz de hacer ImageMagick, ahora mezclemos esto con la potencia de la consola, y tendremos una de las herramientas más potentes jamás inventadas en cuanto a imagen digital se refiere.

Modificando imágenes en un directorio

Aunque podremos hacer cualquier tipo de modificación, tal y como hemos visto anteriormente, haremos un ejemplo en el que redimensionaremos todos los archivos de un directorio al 50% (muy útil si preparamos en un directorio muchos archivos directamente de una cámara digital para subir a Internet):

$ mogrify -resize 50% directorio/*

Con mogrify podemos hacer lo mismo que con convert, sólo que el fichero de origen y de destino es el mismo.

Aunque… ¿y si queremos guardar los archivos originales? Tendremos que hacer un pequeño script para convertir los archivos de un directorio y guardarlos en otro direcorio diferente; pero… y si son muchos ? Tenemos algunas posibilidades:

$ for f in test/*.jpg; do convert -monitor -resize 50% $f “test2/”${f##*/}; done

Si tenemos todos los archivos en el directorio test, queremos redimensionar al 50% y guardarlos en test2/ con el mismo nombre que tienen. ${f##*/} elimina el directorio del nombre del archivo, es lo mismo que la orden basename. Y con el parámetro -monitor veremos más o menos lo que está haciendo convert en cada momento.

Aunque, tal vez queramos un indicador de progreso para saber más o menos cuánto queda y cuántos archivos llevamos, lo podemos hacer con bc y Xdialog:

1
2
3
4
5
6
7
8
9
archivos=`ls test/*.jpg`;
total=`echo $archivos | wc -w`;
hecho=0;
for f in $archivos;
do
convert -resize 50% $f "test2/"${f##*/};
let hecho=$hecho+1;
echo "scale=0;$hecho*100/$total"|bc;
done | Xdialog --gauge "Progreso..." 10 100 0

En este ejemplo, separamos en variables los archivos a tratar y contamos el total de archivos a procesar. La variable hecho contará cuántos archivos ha convertido hasta el momento.

Luego en el bucle principal convertiremos los archivos e iremos incrementando la variable hecho en 1. Más adelante, con la ayuda de bc calculamos el porcentaje hecho. El bucle lo pasamos con una pipe a Xdialog, con su opción de gauge con lo que iremos indicando el proceso a medida que se vayan convirtiendo archivos.

Añadiendo un texto a todas las imágenes

Podemos, además, añadir un texto a todas las imágenes con un formato determinado para incluir el nombre de fichero, ancho, alto, la etiqueta de fichero, etc:

$ mogrify -gravity South -pointsize 20 -fill black -annotate ‘%f %b bytes” *

Con lo que anotaremos dentro de la imagen en color negro el nombre del archivo y el tamaño en bytes. Para esto tenemos algunas palabras clave como %f y %b en este enlace: Imagemagick.org (Identify -format).

hamburguesonAunque tal vez queramos poner un texto diferente a cada foto. Imaginemos que tenemos muchos ficheros jpg y muchos ficheros txt. Los ficheros txt tienen el mismo nombre que los jpg y sólo varía la extensión; y queremos plasmar el texto de los txt en los ficheros jpg:

1
2
3
4
5
6
7
8
9
10
11
for f in *.jpg;
do
if [ -r ${f%.*}.txt ];
then
txt=`cat ${f%.*}.txt`;
else
txt="Mis guisos 2010";
fi;
echo $txt en $f;
mogrify -gravity North -pointsize 15 -fill white -annotate 0x0+10+10 "$txt" $f;
done

Esta vez, si encontramos el fichero de texto con el mismo nombre que la imagen, solo que con extension txt escribiremos ese texto en la parte superior de la imagen; pero si no, escribiremos “Mis guisos 2010″ y así recorriendo todos los archivos jpg del directorio.

Una animación sencilla

destinVamos a crear una transición entre dos imágenes. Y para el ejemplo, hemos utilizado estas dos imágenes:

$ convert -size 720×576 xc:black -gravity Center -fill white -pointsize 250 -font impact -draw ‘text 0 0 A’ a.jpg

$ convert -size 720×576 xc:black -gravity Center -fill white -pointsize 250 -font impact -draw ‘text 0 0 B’ b.jpg

Para realizar el fundido hacemos lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
n_imgs=30; # Número de imágenes
s=`echo "scale=2;100/$n_imgs" | bc` # Porcentaje de inremento de cada paso.
o=0;   # Visibilidad de la imagen B
d=100; # Visibilidad de la imagen A
f=0;   # Fotograma actual
while (( $f &lt; 20 ));
do
convert -compose blend -set "option:compose:args" $o,$d a.jpg b.jpg -composite dest/anim_$f.jpg;
o=`echo "scale=2;$o+$s" | bc`;
d=`echo "scale=2;$d-$s" | bc`;
f=$(($f+1));
echo "Frame: $f O=$o D=$d";
done

Con esto se creará la secuencia de $n_imgs (que al principio le damos valor 30) imágenes en el directorio dest/; todas empezando por anim_.

Si queremos crear un gif animado con esto, podemos hacer lo siguiente:

$ convert dest/anim_{0..29}.jpg trans.gif

Montando un servidor FTP en Linux

Domingo, 21 de Marzo de 2010 Gaspar Fernández Sin comentarios

Es algo que me hacía ilusión aprender y sabía que debía haber algo para hacerlo. De hecho me he encontrado con varias opciones, entre ellas: FTPFS (ya obsoleta, por culpa de LUFIS que entró para el kernel 2.6.14), pero he estado viendo una alternativa mucho mejor FUSE (Filesystem in Userspace). Éste último tiene soporte para muchísimos sistemas de archivos, se pueden montar CVS, FTP, SSH, ficheros RAR, dispositivos bluetooth, particiones NTFS (captive, ntfs-3g…), Blogger… y mucho más… Es algo que me gustaría investigar mucho más a fondo.

Lo del montaje de ftps es una cosa muy sencilla, pero por lo menos que esta entrada en el diario sirva de chuleta (tanto a mí, que cada vez que voy a hacerlo no me acuerdo de cómo es) como a muchos de vosotros.

Lo primero es tener instalado fuse, y lufis:

$ emerge sys-fs/fuse lufis

Ya estamos listos, lo bueno de este sistema es que podemos montar y desmontar siendo usuarios siempre que tengamos el módulo fuse cargado:

$ modprobe fuse

Podemos montar de la siguiente forma, siento /mnt/ftps nuestro punto de montaje (si lo hacemos como usuario, éste tiene que tener permiso sobre ese directorio:

$ lufis fs=ftpfs,host=[servidor],username=[usuario],password=[clave] /mnt/ftps

Lo bueno de esto, es que no sale la contraseña en el listado de procesos, lo malo es que sí que sale el nombre de usuario:

$ ps ax
PID TTY          STAT    TIME COMMAND

466 ?             Ss       0:00 /sbin/udevd –daemon
1599 ?             Ssl       0:00 lufis fs ftpfs host servidor username usuario password                /mnt/ftps

1941 pts/2       R+       0:00 ps ax
4012 ?             Ss       0:00 /usr/sbin/acpid -c /etc/acpi/events

Para desmontar:

$ fusermount -u /mnt/ftps

Si queremos montar un ssh… basta con cambiar ftpfs por sshfs y podemos instalar todos los sistemas de archivos que queramos, del enlace que os pasé antes y disfrutar de lo cómodo que es trabajar de esta forma.

Además esto nos permite poder hacer scripts que interactúen con un servidor FTP mucho más fácilmente.

Categories: Linux Tags: , , , ,

Barra al final del directorio en PHP: 4 formas a elegir

Lunes, 18 de Enero de 2010 Gaspar Fernández Sin comentarios

Cuando queremos representar un directorio; podemos obtenerlo de varias formas: /home/usuario/documentos en *nix o C:\Document and Settings\Usuario\Mis Documentos en Windows. Aunque lo mismo podemos representarlo /home/usuario/documentos/ y C:\Document and Settings\Usuario\Mis Documentos\ (es lo mismo, pero con una barra al final).

El problema en PHP, viene a la hora de llamar archivos de ese directorio, hay ocasiones en las que no sabemos si la variable en donde tenemos almacenado el directorio tiene barra al final o no; entonces debemos hacer alguna función que incluya la barra correspondiente si hace falta.

Estos dos primeros script los he sacado de Jonas John Code Snippets:

1
2
3
4
5
6
7
8
9
10
11
12
function add_ending_slash($path){
 
    $slash_type = (strpos($path, '\')===0) ? 'win' : 'unix';  
    $last_char = substr($path, strlen($path)-1, 1);
 
    if ($last_char != '/' and $last_char != '\') {
        // no slash:
        $path .= ($slash_type == 'win') ? '\' : '/';
    }
 
    return $path;
}
1
2
3
4
5
6
7
8
9
10
11
12
function normalize_path($path){
 
    // DIRECTORY_SEPARATOR is a system variable
    // which contains the right slash for the current
    // system (windows = \ or linux = /)
 
    $s = DIRECTORY_SEPARATOR;
    $path = preg_replace('/[\/\\\]/', $s, $path);
    $path = preg_replace('/'.$s.'$/', '', $path) . $s;
 
    return $path;
}

Para este último, tenemos que hacer antes:

1
define("DIRECTORY_SEPARATOR", (PHP_OS=='Windows') ? '\':'/';

Otras dos alternativas:

1
2
3
4
5
6
function ending_slash($path)
{
  $sep = (PHP_OS == 'Windows')? '\':'/';
  $path .= (substr($path,-1) == $sep)? '':$sep;
  return $path;
}
1
2
3
4
5
6
7
function ending_slash2($path)
{
  $sep = (PHP_OS == 'Windows')? '\':'/';
  if ($path)
    $path .= ($path[strlen($path)-1] == $sep)? '':$sep;
  return $path;
}

La última no es muy elegante, pero ahí queda. Las dos últimas son muy parecidas entre sí. Y es una función interesante para nuestra biblioteca de funciones particular.

Categories: PHP Tags: , , , , ,

Visita otras webs de la red