Renomeador Automático de Arquivos de Mídia

Publicado por Felipe Rafailov 15/07/2009

[ Hits: 5.540 ]

Download nconv.sh




Este script foi criado para renomear arquivos de mídia, como Vídeos e Fotos, porém pode ser utilizado com qualquer tipo de arquivo. É um script bem completo que aceita uma grande variedade de opções de linha de comando.

Para visualizar o resultado sem afetar os arquivos e/ou diretórios, utilize a opção --dummy. Importante usar esta opção para saber como ficariam os arquivos após as transações. Mais informações, incluindo um manual completo com exemplos, pode ser encontrado aqui:

http://maisena.net/scripts/nconv/

  



Esconder código-fonte

#!/bin/bash
# Desenvolvido por Felipe Rafailov
# E-Mail e GTalk: felipe.rafailov@gmail.com

# Quantos diretórios o script irá criar
# 0 - Não cria nenhum diretório (ex. Lost S01E01.avi)
# 1 - Cria um diretório para o seriado (ex. Lost/S01E01.avi)
# 2 - Cria diretórios para o seriado e a temporada (ex. Lost/S01/01.avi)
# 3 - Cria um diretório para cada episódio (ex. Lost/S01/S01E01/S01E01.avi)
DIRLEVEL=1
#DIRLEVEL=0

# Separador usado para o nome do arquivo e/ou diretório
EOW=' '
#EOW='.'
#EOW='_'

# Cria os diretórios (se necessário) e move o arquivo
# 0 - Somente exibe o nome na tela (para visualizar)
# 1 - Efetua a transação, movendo o arquivo (padrão)
MOVE=1

# Quando habilitado, efetua a cópia do arquivo original para seu novo
# destino.
COPY=0

# Habilita o modo verboso
# 0 - Desabilitado (padrão)
# 1 - Habilitado
VERBOSE=0

# No modo interativo, em cada alteração o usuário pode aceitar a sugestão,
# rejeitar, ou propor outro nome para o arquivo.
INTERACTIVE=0

# Os arquivos a serem movidos são armazenados nesta variável
FILES=

# Quando habilitado, o nome completo será atribuído ao arquivo.
# Senão, somente a parte referente ao número do episódio é colocado.
# 0 - Nomes curtos, ex.:    S01E01.avi
# 1 - Nomes compridos, ex.: Lost S01E01.avi
FULLNAME=1

# Permite especificar o diretório de saída, onde os arquivos serão
# criados. Por padrão, usa-se o diretório atual.
#OUTPUT=$PWD
OUTPUT=

# Permite adicionar um nome (inicial) a todos os arquivos alterados.
# Permite também adicionar um contador ao nome, utilizando o caracter
# especial '%'
BASENAME=
COUNT=0

# Esta variável contém uma lista, separada por vírgulas, de todos os termos
# a serem ignorados no nome do arquivo. Outra opção, --ignoreall, permite
# ignorar completamente o nome original do arquivo. O novo nome será formado
# pelo valor de --basedir e --basename.
IGNORE=
IGNOREALL=0

# Contém o nome do arquivo usado como fonte dos nomes dos episódios,
# quando o parâmetro --source é utilizado. O arquivo deve possuir uma
# estrutura específica, ex.:
# S01E01 Nome do Episódio 1 da 1a temporada
SOURCE=

# Função responsável por unir duas strings, utilizada para anexara
# duas strings consecutivamente, separadas por um caractere ou string
#
append(){
  if [ -n "$1" ]; then
    if [[ $1 =~ $2$ ]]; then
      echo "$1"
    else
      echo "${1}${3}${2}"
    fi
  else
    echo "$2"
  fi
}

# Coloca a primeira palavra em Caixa Alta, quando necessário
capitalize(){
  if [ ${#1} -ge 3 ] || [[ "$2" =~ ^$1 ]]; then
    part1="$(echo -n "${1:0:1}" | tr "[:lower:]" "[:upper:]")"
    part2="$(echo -n "${1:1}" | tr "[:upper:]" "[:lower:]")"
    echo "${part1}${part2}"
  else
    echo "$(echo "$1" | tr "[:upper:]" "[:lower:]")"
  fi
}

# Esta função substitui o caractere especial '%' por um contador. Esta função é
# utilizada para interpretar o argumento da opção --basename e, no modo
# interativo, também a opção de escolher o nome do arquivo manualmente.
replace_counter() {
  NEWCOUNT=$COUNT
  if [ -n "$1" ] && [[ $1 =~ '(\*?%+)' ]]; then
    t_mask="${BASH_REMATCH[1]}"
    if [ "${t_mask:0:1}" = "*" ]; then
      if [ $INTERACTIVE -eq 1 ]; then
        NEWCOUNT=1
      fi
      t_mask="${t_mask:1}"
    else
      NEWCOUNT=$(($NEWCOUNT+1))
    fi
    t_count=$NEWCOUNT
    while [ ${#t_count} -lt ${#t_mask} ]; do
      t_count="0$t_count"
    done
    NEWNAME="$(echo "$1" | sed "s/*\?$t_mask/$t_count/")"
  else
    NEWNAME="$1"
  fi
  echo "NEWNAME=\"$NEWNAME\""
  echo "NEWCOUNT=$NEWCOUNT"
}

show_help(){
echo "Uso: $(basename $0) [-i] [-d] '.' [-l] 3 arquivo1.avi arquivo2.avi ...
-h --help          Mostra informações resumidas sobre o uso do programa e sai.
-v --verbose       Mostra informações adicionais na saída, informando o usuário
                   sobre o estado atual da execução.
   --dummy         Mostra as operações que serão realizadas, porém não efetua
                   nenhuma mudança no disco rígido. Também implica em -v.
-s --short         Utiliza nomes curtos para os arquivos.
-c --copy          Copia o arquivo original para seu novo destino, em vez de
                   movê-lo. Útil para quando os arquivos originais estiverem
                   em mídias de somente-leitura (CD's ou DVD's).
-i --interactive   Habilita o modo interativo, que permite visualizar as
                   alterações a serem realizadas no disco rígido antes de
                   aceitá-las.
   --ignore a,b,c  Permite especificar uma lista (separada por vírgulas) de
                   termos que não serão consideradas como parte do novo nome do
                   arquivo. Esta opção não distingue maiúsculas/minúsculas.
   --ignoreall     Ignora completamente o nome original do arquivo. O novo nome
                   deverá ser baseado nos valores de --basename e --basedir.
-d --delimiter d   Altera o caractere delimitador. Por padrão, usa-se espaço.
   --basename n    Permite adicionar um nome ao início de cada arquivo a ser
                   alterado. Você pode acicionar um contador com o caractere
                   '%'. Usando-o múltiplas vezes seguidas permite adicionar
                   mais dígitos ao contador. Ex.:
                      --basename '%%' = 01, --basename '%%%' = 001, etc.
   --basedir n     Permite especificar o diretório de base onde os novos
                   diretórios e arquivos serão criados.
   --source file   Esta opção permite especificar um arquivo para servir de
                   fonte para os nomes dos episódios. O arquivo deve possuir
                   o seguinte formato:
                      S01E01 Nome do Episódio 1 da 1a Temporada
-l --level l       Altera o número de sub-diretórios que serão criados. Aceita
                   valores de 0-3. O padrão é 1. Por exemplo:
                      nível 0: Lost S01E01.avi
                      nível 1: Lost/Lost S01E01.avi
                      nível 2: Lost/S01/Lost S01E01.avi
                      nível 3: Lost/S01/S01E01/Lost S01E01.avi"
}

# Faz a leitura das opções de linha de comando do script
while [ -n "$1" ]; do
  case "$1" in
  -h | --help)
    show_help
    exit 0
    ;;
  -v | --verbose)
    VERBOSE=1
    ;;
  -i | --interactive)
    MV_OPTS="-i"
    INTERACTIVE=1
    ;;
  --dummy)
    MOVE=0
    VERBOSE=1
    ;;
  -s | --short)
    FULLNAME=0
    ;;
  -c | --copy)
    COPY=1
    ;;
  -d | --delimiter)
    shift
    if [ "$1" = '/' ]; then
      echo ERRO: Delimitador inválido
      exit 1
    fi
    EOW="$1"
    ;;
  --basedir)
    shift
# Não é necessário verificar o diretório de saída, isso é feito mais adiante
    OUTPUT="$(echo $1 | sed 's/\/$//')"
    ;;
  --basename)
    shift
    BASENAME="$1"
    ;;
  --source)
    shift
    if [ -f "$1" ] && [ -r "$1" ]; then
      SOURCE="$1"
    else
      echo "AVISO: Não pode ler do arquivo de fonte $1"
    fi
    ;;
  --ignore)
    shift
    IGNORE=",$(echo "$1" | sed 's/[[:space:]]\+/,/g'),"
    ;;
  --ignoreall)
    IGNOREALL=1
    ;;
  -l | --level)
    shift
    if [[ $1 =~ ^[[:digit:]]+$ ]] && [ $1 -ge 0 ] && [ $1 -le 3 ]; then
      DIRLEVEL=$1
    else
      echo "ERRO: Nível inválido. Utilize valores de 0 a 3."
      echo "Veja $(basename $0) --help para maiores informações."
      exit 1
    fi
    ;;
  *)
    FILES=$(append "$FILES" "$1" \;)
    ;;
  esac
  shift
done

# Verifica se foram passados arquivos como parâmetro
if [ -z "$FILES" ]; then
  echo ERRO: Nenhum arquivo fornecido para renomear
  exit 1
fi

# Caso alguma das opções, --basedir ou --basename for usada, faz
# o tratamento do nome, como espaços.
if [ -n "$OUTPUT" ]; then
  OUTPUT="$(echo $OUTPUT | sed "s/[[:space:]]\+/${EOW}/g")"
fi
if [ -n "$BASENAME" ]; then
  BASENAME="$(echo $BASENAME | sed "s/[[:space:]]\+/${EOW}/g")"
fi

# A variável IFS é alterada pois no caso da variável FILES, os arquivos
# são separados por ponto-e-vírgula ';'
IFS=';'

for file in $FILES; do

# O nível de expansão pode variar conforme o nome do arquivo. Caso não seja
# encontrado um número de episódio, um nível inferior (1) é usado.
  t_dirlevel=$DIRLEVEL
  t_short=$FULLNAME
  t_basename="$BASENAME"

# Pula arquivos não existentes e diretórios (quando não estiver usando --dummy)
  if [ -d "$file" ] || [ $MOVE -eq 1 ] && [ ! -f "$file" ]; then
    if [ -d "$file" ]; then
      echo "ERRO: \"$file\" é um diretório."
    elif [ ! -f "$file" ]; then
      echo "ERRO: Arquivo não encontrado: \"$file\""
    fi
    continue
  fi

# Verifica o diretório de saída (quando especificado) ou o diretório atual
  if [ -n "$OUTPUT" ]; then
    t_ckdir="$OUTPUT"
  else
    t_ckdir=$PWD
  fi
  while [ ! -d $t_ckdir ]; do
    t_ckdir="$(dirname "$t_ckdir")"
  done
  if [ ! -w "${t_ckdir}" ] && [ $MOVE -eq 1 ]; then
    echo "ERRO: Não posso criar arquivo(s)/diretório(s) de saída em $t_ckdir"
    exit 1
  fi

  t_filename="$(basename "$file")"
  t_extension="$(echo $t_filename | awk -F . '{print $NF}')"

# Verifica se o arquivo possui extensão
  if [ "$t_filename" = "$t_extension" ]; then
    unset t_extension
  else
    t_extension=".$t_extension"
  fi
  if [ $IGNOREALL -eq 0 ]; then
    t_filename=$(basename "$file" "$t_extension" | sed 's/[[:punct:]]\+/ /g')
  else
    unset t_filename
  fi

  unset IFS
  for t_part in $t_filename ; do
# Remove partes indesejadas do nome do arquivo, passadas pelo argumento
# --ignore
    t_check="$(echo "$t_part" | sed "s/[[:digit:]]/%/g")"
    if [ -n "$(echo $IGNORE | grep -i ",$t_check,")" ] ||
       [ -n "$(echo $IGNORE | grep -i ",$t_part,")" ]; then
      continue
# Procura pelo descritivo do número do episódio, geralmente
# SXXEYY, onde XX é a temporada e YY é o número do episódio.
# Ex.: S01E01
    elif [[ $t_part =~ ^[sS]?[[:digit:]]+[eE]?[[:digit:]]*$ ]]; then
      filename="$(append "$filename" "$(echo $t_part | tr se SE)" "$EOW")"
      break
# Caso seja um nome composto alfanumérico, do tipo arquivo01
    elif [[ $t_part =~ ^([[:alpha:]]+)([[:digit:]]+)$ ]]; then
      t_alpha_part="${BASH_REMATCH[1]}"
      t_numeric_part="${BASH_REMATCH[2]}"
      t_check="$(echo "$t_numeric_part" | sed "s/[[:digit:]]/%/g")"
      if [ -n "$(echo $IGNORE | grep -i ",$t_alpha_part,")" ] ||
         [ -n "$(echo $IGNORE | grep -i ",$t_check,")" ] ||
         [ -n "$(echo $IGNORE | grep -i ",$t_numeric_part,")" ]; then
        continue
      else
        t_part="$(capitalize "$t_alpha_part" "$t_filename")"
        dirname="$(append "$dirname" "$t_part" "$EOW")"
        filename="$(append "$filename" "$t_numeric_part" "$EOW")"
      fi
    else
      t_part="$(capitalize "$t_part" "$t_filename")"
      dirname="$(append "$dirname" "$t_part" "$EOW")"
    fi
  done

# Quando o nome do diretório for vazio, altera o t_dirlevel para
# compensar a falta do nome
  if [ -z "$dirname" ] && [ -n "$filename" ]; then
    if [ $t_dirlevel -eq 1 ]; then
      t_dirlevel=2
    elif [ $t_dirlevel -eq 2 ]; then
      t_dirlevel=3
    fi
  fi

# Caso o nome do arquivo (filename) esteja vazio, este recebe o nome do
# diretório
  if [ -z "$filename" ] && [ -n "$dirname" ]; then
    filename="$dirname"
    if [ $t_dirlevel -gt 1 ]; then
      t_dirlevel=1
    fi
  fi

# Altera o nome entre o nome curto e longo, de acordo com a
# opção --short. Também verifica se o nome do diretório e do arquivo
# são iguais, para evitar nomes repetidos.
  if [ $t_short -eq 1 ] && [ ! "$dirname" = "$filename" ]; then
    t_fullname="$(append "$dirname" "$filename" "$EOW")"
  else
    t_fullname=$filename
  fi

# Caso exista um prefixo (--basename), verificar a existencia do
# caracter de contador e substituí-lo pelo número do contador
  eval "$(replace_counter "$t_basename")"
  t_basename="$NEWNAME"
  COUNT=$NEWCOUNT

# Adiciona um prefixo a todos os arquivos alterados (usando a opção --basename)
  t_fullname="$(append "$t_basename" "$t_fullname" "$EOW")"

# Quando estiver usando a opção --source, obter o nome do episódio através do
# arquivo de fonte especificado pelo parâmetro, tratar a entrada e anexar este
# no nome do arquivo.
  if [ -n "$SOURCE" ] && [[ $filename =~ ^S?[[:digit:]]+E?[[:digit:]]*$ ]]; then
    t_episode="$(cat "$SOURCE" | grep "$filename" | cut -d' ' -f2- |
      sed "s/[[:space:]]\+/${EOW}/g")"
    t_fullname="$(append "$t_fullname" "$t_episode" "$EOW")"
  fi

# Verifica se o nome do arquivo é vazio. Caso seja, significa que o script falhou
# em determinar o nome do arquivo, logo ele será ignorado
  if [ -z "$t_fullname" ]; then
    echo "ERRO: Falha ao determinar nome do arquivo. Pulando arquivo $file"
    continue
  else
# Adiciona a extensão do arquivo no nome completo
    t_extension="$(echo $t_extension | tr "[[:upper:]]" "[[:lower:]]")"
    t_fullname="$(append "$t_fullname" "$t_extension")"
  fi

# Caso o DIRLEVEL seja maior ou igual a 2, o seguinte código descobre o nome do
# diretório intermediário, que é formado pelos primeiros dígitos numéricos do
# código do episódio, ou, no caso de somente haver números, usa-se a dezena ou
# centena.
# Ex.:
# S01E01 => S01
# 115 => 100
  if [ $t_dirlevel -ge 2 ] && [[ $filename =~ ^(S?[[:digit:]]+) ]]; then
    t_midname=${BASH_REMATCH[1]}
    if [ ! "${t_midname:0:1}" = "S" ]; then
      t_size=${#t_midname}
      if [ $t_size -le 2 ]; then
        if [ $t_size -eq 1 ]; then
          t_midname="$(append "0" "$t_midname")"
        fi
        t_size=1
      else
        t_size=$(expr ${#t_midname} - 2)
      fi
      t_xpon=$((10**$t_size))
      t_midname=$(expr $t_midname - $(expr $t_midname % $t_xpon))
    fi
  fi

# Estabelece o nível de diretórios de acordo com a variável
# DIRLEVEL (t_dirlevel)
  case $t_dirlevel in
  0)
    newname="$t_fullname"
    ;;
  1)
    newname="$(append "$dirname" "$t_fullname" \/)"
    ;;
  2)
    filename="$(append "$t_midname" "$t_fullname" \/)"
    newname="$(append "$dirname" "$filename" \/)"
    ;;
  3)
    filename="$(append "$t_midname" "$filename" \/)"
    filename="$(append "$filename" "$t_fullname" \/)"
    newname="$(append "$dirname" "$filename" \/)"
    ;;
  esac

# Permite adicionar um diretório de saída usando a opção --basedir
  newname="$(append "$OUTPUT" "$newname" \/)"

# Se no modo interativo, permite ao usuário aceitar o nome, rejeitar,
# ou propor um nome próprio. O usuário também pode cancelar o modo
# iterativo e sair do programa.
  t_exit=0
  t_skip=0
  while [ $INTERACTIVE -eq 1 ] && [ $t_exit -eq 0 ]; do
    echo
    if [ $COPY -eq 1 ]; then
      t_action=Copiar
    else
      t_action=Mover
    fi
    echo $t_action \"$file\" \=\> \"$newname\"
    echo -n "Escolha uma opção, ou ? para ajuda: "
    read t_option
    while [[ ! $t_option =~ ^[12345?arpcsARPCS]$ ]]; do
      echo "Opção Inválida: $t_option"
      read t_option
    done

    case $t_option in
    a | A | 1)
      t_exit=1
      ;;
    r | R | 2)
      unset dirname
      unset filename
      IFS=';'
      t_exit=1
      t_skip=1
      ;;
    p | P | 3)
      echo -n "Digite o nome do arquivo: "
      read t_newname
      while [ -z "$t_newname" ]; do
        echo -n "Digite o nome do arquivo: "
        read t_newname
      done
      eval "$(replace_counter "$t_newname")"
      newname="$(echo $NEWNAME | sed "s/[[:space:]]\+/${EOW}/g")"
      COUNT=$NEWCOUNT
      t_exit=1
      ;;
    c | C | 4)
      INTERACTIVE=0
      unset MV_OPTS
      t_exit=1
      ;;
    s | S | 5)
      exit 0
      ;;
    ?)
      echo Opção 1. a Aceitar nome escolhido, alterar nome do arquivo
      echo Opção 2. r Rejeitar nome escolhido, manter nome original
      echo Opção 3. p Propor outro nome para o arquivo
      echo Opção 4. c Cancelar o modo interativo, continuar no automático
      echo Opção 5. s Sair do programa
      ;;
    esac
  done
  
  if [ $t_skip -eq 1 ]; then
    continue
  fi

  t_return=0

# Move/renomeia o arquivo para seu novo destino e cria o diretório caso
# seja necessário
  if [ $MOVE -eq 1 ]; then
    t_newdir=$(dirname "$newname")
    if [ ! -d "$t_newdir" ] && [ ! -f "$t_newdir" ]; then
      mkdir -p "$t_newdir"
    fi
    if [ $COPY -eq 1 ]; then
      t_cmd=cp
    else
      t_cmd=mv
    fi
    $t_cmd $MV_OPTS "$file" "$newname"
    t_return=$?
  fi

# Habilita a saída em modo verboso
  if [ $VERBOSE -eq 1 ]; then
    [[ $t_return -ne 0 ]] && echo -n "ERRO: " || echo -n "OK: "
    echo $newname
  fi

# Limpa as variáveis utilizadas
  unset dirname
  unset filename
  IFS=';'
done

Scripts recomendados

Script para ligar/desligar o Conky no Fluxbox

Compactação do relatório do Squid/Sarg

Criando pdf para impressão de livros

CGI + Shell

Backup de bases de dados individuais do PostgreSQL


  

Comentários
[1] Comentário enviado por Lisandro em 15/07/2009 - 07:39h

Muito bom, bem comentado e claro. Valeu.

[2] Comentário enviado por feraf em 15/07/2009 - 16:22h

Atualizei o Script, que em sua versão 1.1 traz vários melhoramentos na função --ignore. Esta e as próximas versões podem ser baixadas do site http://maisena.net/scripts/nconv/, a documentação foi atualizada para incorporar as novas alterações. Agora é possível usar o coringa * e ** na opção --ignore.


Contribuir com comentário




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner
Linux banner
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts