I wrote this shell script to convert around 7 million PDF’s. The initial Java version had trouble with performance, it crashed a lot and used way to much memory. So old bash again did a clean and fast job.
Overview:
We had many local folders organized by year / day-month-year/ subparts.
That we had to convert and send to amazon. We had divided into pages and articles.
The requirements:

  • Each PDF generated 3 size a P (small) M (Medium G (Large).
  • Convert then send to Amazon S3
  • Generate RGB Format JPG’s
  • Two different buckets, public and private
  • Only the G size goes to private.
  • Had to be very Fast, about 9TB max time of 2months.
  • Clean and usefull log

I used few open-source technologies:

Some of the problems:
No RGB, PDF conversion to jpg using ‘convert’ does not generate a RGB. Solution:a TMP_JPG in RGB. For that reason we used first the cmd:
gs -q -sDEVICE=jpeg -dNOPAUSE -dBATCH -dSAFER -r150 -sOutputFile=${TMP_JPG} $PDF
Speed considerations:
Copying: Instead of copying each image to amazon we used the sync s3cmd for an entire folder:
s3cmd -v –no-preserve –acl-private –force sync $1 $2″
s3cmd -v –no-preserve –acl-public –skip-existing sync $1 $2″
Local disk I/O, started to be a problem as we started about 8 of these scripts per server. To speed things up we used a disk in memory, or what I call RAM HD (howto link).
Break the list, a very poor man multi-thread way. Since the list sometime reached about 150K itens we started spliting it in 3 parts, that way we could send in parallel. The function geralista() does that. If the list was previously generated it reused. The list generation is a simple find

Running the script, we used:
nohup /opt/convert_send -t pagina -e diretorio -p 0 -c /mnt/Acervo/PDFs_XMLs_paginas/lobo/1949/12/31/ -f -G > /var/log/convert_1949_12_31_PAG.log &
SCRIPT USAGE, HELP (in PT)
Usage: /opt/convert -t (artigo/pagina) -e (lista/diretorio) -p <(0), (1),(2),(3)> -c <PDF DIR> -f -PMG
-t TIPO artigo ou pagina
-e ENTRADA lista ou diretorio
-p PARTES partes a dividir ou nao a lista 0 nao divide, 1,2 ou 3.Esperar que a lista seja gerada para executar o 2 ou 3
-c CAMINHO do diretorio ou lista
-f forca sobrescrever o que ja esta no S3
PMG tamanho das imagens P pequeno M media ou G grande, pode usar combinacoes: P, PM, G, PMG
Example:
/opt/convert -t pagina -e lista -p 0 -c /tmp/lista.txt -f -PMG
 
 
Here is the script:

#!/bin/bash
####################################################################################
#script to convert pdf's to jpg's (RGB) and send it to AWS S3.
#by Felipe Ferreira Jul 2013
#Version 4.0
#BUGS 17/07
#OK - PDF upercase
#OK - COPIAR PDF ao TMP_LOCAL, corrigido
#OK - ENVIA AO LUGAR ERRADO, corrigido
#BUG 29/07
#OK - Pegando L e A da img anterior ja convertida, causa problema na M e G, corrigido
#MELHORIAS 29/07
#OK- envair com ACL publico --acl-public e --force
#MELHORIAS AGOSTO
# Passar Argumentos
### EDITAR INICIO ####
TMP_LOCAL="/mnt/Mem/Thumbnail" #
BUCKET="BUCKET-PUB"
BUCKET_G="BUCKET-PRIV"
### EDITAR FIM ####
DATA=""
LISTA=""
I=1
VER="4.0"
IM=""
FORCAR=0
TIPO=""
ENTRADA=""
PARTES=0
while getopts t:e:p:c:fhPMG option
do
case "${option}"
in
h)
help
;;
t)
TIPO=${OPTARG}
;;
e)
ENTRADA=${OPTARG}
;;
p)
PARTES=${OPTARG}
;;
c)
CAMINHO=${OPTARG}
;;
f)
FORCAR=1
;;
P)
IM="${IM}P"
;;
M)
IM="${IM}M"
;;
G)
IM="${IM}G"
;;
esac
done
echo -e "\nParametros: TIPO $TIPO ENTRADA $ENTRADA PARTES $PARTES CAMINHO $CAMINHO IM $IM FORCAR $FORCAR\n"
 
########################################
#-------------FUNCTIONS----------------#
########################################
function help {
echo "Versao $VER by Felipe Ferreira - Agosto 2013"
echo -e "Script para converter pdf a jpg e enviar ao S3 \n"
echo -e "Usage: ${0} -t (artigo/pagina) -e (lista/diretorio) -p <(0), (1),(2),(3)> -c <PDF DIR> -f -PMG\n\n"
echo " -t TIPO artigo ou pagina"
echo " -e ENTRADA lista ou diretorio"
echo " -p PARTES partes a dividir ou nao a lista 0 nao divide, 1,2 ou 3.Esperar que a lista seja gerada para executar o 2 ou 3"
echo " -c CAMINHO do diretorio ou lista"
echo " -f forca sobrescrever o que ja esta no S3"
echo " PMG tamanho das imagens P pequeno M media ou G grande, pode usar combinacoes: P, PM, G, PMG"
echo -e "\nExample: \n ${0} -t pagina -e lista -p 0 -c "/tmp/lista.txt" -f -PMG \n\n"
echo "Existem outros parametros configuraveis via variaveis dentro do script como:"
echo "BUCKET e BUCKET_G, S3 PM(public) e G(private) \nTMP_LOCAL Diretorio Temporario"
exit 1
}
if [ $# -eq 0 ] || [ $# -le 7 ] ; then
echo "Parametros incorretos!"
help
fi
function timer()
{
if [[ $# -eq 0 ]]; then
echo $(date '+%s')
else
local stime=$1
etime=$(date '+%s')
if [[ -z "$stime" ]]; then stime=$etime; fi
dt=$((etime - stime))
ds=$((dt % 60))
dm=$(((dt / 60) % 60))
dh=$((dt / 3600))
printf '%d:%02d:%02d' $dh $dm $ds
fi
}
function convert()
{
#PARAMETERS: #1 FILE ORIGINAL(PDF)
PDF=$1
JPG=$2
if [ -e "$TMP_JPG" ]; then
#Pegar Largura e Tamanho
IMG_I=`gm identify ${TMP_JPG}`
IMG_L=`echo $IMG_I|awk {'print $3'} |cut -d "x" -f1`
IMG_A=`echo $IMG_I|awk {'print $3'} |cut -d "x" -f2 |cut -d "+" -f1`
IMG_S=`echo $IMG_I|awk {'print $6'}`
echo "_t ja criado, L: $IMG_L A: $IMG_A Tamanho: $IMG_S"
else
#Nao existe converte PDF pra JPG 150 dpi
/usr/bin/gs -q -sDEVICE=jpeg -dNOPAUSE -dBATCH -dSAFER -r150 -sOutputFile=${TMP_JPG} $PDF
if [ -e "$TMP_JPG" ]; then
#Pegar Largura e Tamanho
IMG_I=`gm identify ${TMP_JPG}`
IMG_L=`echo $IMG_I|awk {'print $3'} |cut -d "x" -f1`
IMG_A=`echo $IMG_I|awk {'print $3'} |cut -d "x" -f2 |cut -d "+" -f1`
IMG_S=`echo $IMG_I|awk {'print $6'}`
echo "LARGURA: $IMG_L ALTURA: $IMG_A Tamanho: $IMG_S"
else
echo -e "ERROR AO CRIAR _t PDF INVALIDO /n $PDF /n/n"
fi
fi
case "$2" in
*_p.jpg*)
#"SMALL FILE"
if [ "$TIPO" == "artigo" ]; then
IMG_L_P=215
IMG_A_P=215
elif [ "$TIPO" == "pagina" ]; then
IMG_L_P=156
IMG_A_P=250
else
echo "Error tipo deve ser artigo ou pagina $TIPO"
exit 3
fi
#_p se L menor que 215 nao faz nada e se A menor que 215 nao faz nada - copiar T pra P
if [ "$IMG_L" -le "$IMG_L_P" ] && [ "$IMG_A" -le "$IMG_A_P" ]; then
echo "Copiando _t pra _p, L: $IMG_L A: $IMG_A Tamanho: $IMG_S"
CMD_CONVERT_OPT=""
/bin/cp -f $TMP_JPG $JPG
else
# se L maior que A - converte L pra X
if [ "$IMG_L" -gt "$IMG_A" ]; then
CMD_CONVERT_OPT="-density 72 -resize ${IMG_L_P}x"
else
x=`expr $IMG_L \* $IMG_A_P / $IMG_A`
echo "Valor de X: $x "
echo "Verificando se valor de X eh maior que largura maxima"
if [ "$x" -gt "$IMG_L_P" ]; then
#faco conversao com largura maxima
echo "X maior que largura maxima. Convertendo arquivo com largura maxima e X..."
CMD_CONVERT_OPT="-density 72 -resize ${IMG_L_P}x"
else
CMD_CONVERT_OPT="-density 72 -resize x${IMG_A_P}"
fi
fi
fi
;;
*_m.jpg*)
#"MEDIUM FILE"
if [ "$IMG_L" -le 300 ] && [ "$IMG_A" -le 480 ]; then
echo -e "Copiando _t pra _m L: $IMG_L A: $IMG_A Tamanho: $IMG_S"
CMD_CONVERT_OPT=""
/bin/cp -f $TMP_JPG $JPG
else
# se L maior que A - converte L pra X
if [ "$IMG_L" -gt "$IMG_A" ]; then
CMD_CONVERT_OPT="-density 72 -resize 300x"
else
x=`expr $IMG_L \* 480 / $IMG_A`
echo "Valor de X: $x "
echo "Verificando se valor de X eh maior que largura maxima"
if [ "$x" -gt 300 ]; then
#faco conversao com largura maxima
echo "X maior que largura maxima. Convertendo arquivo com largura maxima e X..."
CMD_CONVERT_OPT="-density 72 -resize 300x"
else
CMD_CONVERT_OPT="-density 72 -resize x480"
fi
fi
fi
;;
*_g.jpg*)
#"BIG FILE"
if [ "$IMG_L" -le 1800 ] && [ "$IMG_A" -le 2900 ]; then
echo "Copiando _t pra _g, L: $IMG_L A: $IMG_A Tamanho: $IMG_S"
CMD_CONVERT_OPT=""
/bin/cp -f $TMP_JPG $JPG
else
# se L maior que A - converte L pra X
if [ "$IMG_L" -gt "$IMG_A" ]; then
CMD_CONVERT_OPT="-density 72 -resize 1800x"
else
x=`expr $IMG_L \* 2900 / $IMG_A`
echo "Valor de X: $x "
echo "Verificando se valor de X eh maior que largura maxima"
if [ "$x" -gt 1800 ]; then
#faco conversao com largura maxima
echo "X maior que largura maxima. Convertendo arquivo com largura maxima e X..."
CMD_CONVERT_OPT="-density 72 -resize 1800x"
else
CMD_CONVERT_OPT="-density 72 -resize x2900"
fi
fi
fi
;;
esac
if [ -n "$CMD_CONVERT_OPT" ]; then
CMD_CONVERT="/usr/local/bin/gm convert $TMP_JPG ${CMD_CONVERT_OPT} $JPG"
#echo "CMD_CONVERT $CMD_CONVERT"
RESULT_C=`eval ${CMD_CONVERT}`
IMG_I=`gm identify ${JPG}`
IMG_L=`echo $IMG_I|awk {'print $3'} |cut -d "x" -f1`
IMG_A=`echo $IMG_I|awk {'print $3'} |cut -d "x" -f2 |cut -d "+" -f1`
IMG_S=`echo $IMG_I|awk {'print $6'}`
echo "Converteu ${IMG_L}x${IMG_A} para $IMG_S"
fi
}
function enviasync()
{
#PARAMTERS: 1 dir local 2 dir onde sera enviado 3 extensao do arquivo
#echo "ENVIANDO DE $1 A $2"
if [ "$FORCAR" -eq "1" ]; then
echo "Removendo arquivos anteriores do bucket"
CMD_DELETE="/opt/s3cmd/s3cmd del $2$3"
echo "Forcar sobrescrever $FORCAR"
CMD_ENVIA="/opt/s3cmd/s3cmd -v --no-preserve --acl-private --force sync $1 $2"
else
echo "Nao forcar sobrescrever $FORCAR"
CMD_ENVIA="/opt/s3cmd/s3cmd -v --no-preserve --acl-public --skip-existing sync $1 $2"
fi
echo "CMD_DELETE: $CMD_DELETE"
RESULT_D=`eval ${CMD_DELETE}`
echo $RESULT_D
echo "CMD_ENVIA: $CMD_ENVIA"
RESULT_E=`eval ${CMD_ENVIA}`
echo $RESULT_E
}
function geralista()
{
# Gera lista se entrada for diretorio ou usa lista existente
if [ "$ENTRADA" == "diretorio" ]; then
ANO_DIR=`echo ${CAMINHO} | sed 's/[^0-9]*//g' |cut -c 1-4`
LISTA="/tmp/lista_${ANO_DIR}.txt"
LISTA_N=`echo $LISTA| cut -d "." -f1`
rm -f ${LISTA_N}* && echo "Limpou listas antigas $LISTA_N"
echo "DIRETORIO = $CAMINHO"
elif [ "$ENTRADA" == "lista" ]; then
LISTA=$CAMINHO
echo "LISTA = $LISTA"
else
echo -e "Error parametro entrada incorreto, deve ser diretorio ou lista\n\n"
help
fi
LISTA_N=`echo $LISTA| cut -d "." -f1`
echo -e "ENTRADA: $ENTRADA\nCAMINHO: $CAMINHO\nLISTA: $LISTA \nPARTES: $PARTES"
#VERIFICA SE LISTA JA FOI CRIADA
if [ ! -f $LISTA ] && [ "$ENTRADA" == "diretorio" ]; then
echo -e ".Gerando lista de arquivos, lista (${PARTES}/3)"
find $CAMINHO -iname '*.pdf' > $LISTA
sort -n $LISTA -o $LISTA
elif [ ! -f $LISTA ] && [ "$ENTRADA" == "lista" ]; then
echo -e "Error lista $LISTA nao existe"
exit 1
fi
TOTAL=`wc -l $LISTA |cut -d " " -f1`
# divide lista em 3
DIVIDE=`expr $TOTAL / 3`
CMD_L="cat $LISTA | awk -v lines='$DIVIDE' -v fmt='${LISTA_N}_0%d.txt' '{print>sprintf(fmt,1+int((NR-1)/lines))}'"
# echo $CMD_L
RESULT_L=`eval $CMD_L`
LISTA=$LISTA_N
#CONFIGURA LISTA DE ACORDO COM EXTENSAO DO SCRIPT
if [ "$PARTES" -eq 1 ] && [ -f "${LISTA}_01.txt" ]; then
LISTA="${LISTA}_01.txt"
elif [ "$PARTES" -eq 2 ] && [ -f "${LISTA}_02.txt" ]; then
LISTA="${LISTA}_02.txt"
elif [ "$PARTES" -eq 3 ] && [ -f "${LISTA}_03.txt" ]; then
LISTA="${LISTA}_03.txt"
#as vezes cria o 04 mover ao 03
if [ -f "${LISTA}_04.txt" ]; then
CMD_C="cat ${LISTA}_04.txt >> $LISTA && rm -f ${LISTA}_04.txt"
RESULT_C=`eval $CMD_C`
fi
elif [ "$PARTES" -eq 0 ] && [ -f "${LISTA}.txt" ]; then
LISTA="${LISTA}.txt"
else
echo "Error, gracao de lista com problemas ou arquvio nao encontrado"
exit 1
fi
#incremente final da lista, ou ultimo diretorio nao sera enviado, bug fix
echo "/tmp/nada/">>${LISTA}
TOTAL=`wc -l $LISTA |cut -d " " -f1`
echo -e ".Inciando conversao e envio de $CAMINHO na Lista: $LISTA (TOTAL ${TOTAL})"
}
########################################
#---------------- MAIN ----------------#
########################################
t=$(timer)
geralista
IFS=$'\n' # new field separator, the end of line
for LINE in $(cat ${LISTA})
do
NOME_ARQUIVO=`echo ${LINE} |awk -F/ '{print $NF}'`
NOME_ARQUIVO_JPG_P=`echo ${NOME_ARQUIVO}|sed 's/\.pdf/\_p.jpg/gI'`
NOME_ARQUIVO_JPG_M=`echo ${NOME_ARQUIVO}|sed 's/\.pdf/\_m.jpg/gI'`
NOME_ARQUIVO_JPG_T=`echo ${NOME_ARQUIVO}|sed 's/\.pdf/\_t.jpg/gI'`
NOME_ARQUIVO_JPG_G=`echo ${NOME_ARQUIVO}|sed 's/\.pdf/\_g.jpg/gI'`
NOME_DIR=`echo ${LINE} |sed -e s/"$NOME_ARQUIVO"//g -e 's/\/mnt\/Acervo//g'`
NOME_DIR_NOVO="${TMP_LOCAL}${NOME_DIR}"
NOME_ARQUIVO_ENVIO_P="${TMP_LOCAL}${NOME_DIR}${NOME_ARQUIVO_JPG_P}"
NOME_ARQUIVO_ENVIO_M="${TMP_LOCAL}${NOME_DIR}${NOME_ARQUIVO_JPG_M}"
NOME_ARQUIVO_ENVIO_G="${TMP_LOCAL}${NOME_DIR}${NOME_ARQUIVO_JPG_G}"
#LISTA_ARQUIVOS=( $NOME_ARQUIVO_ENVIO_P $NOME_ARQUIVO_ENVIO_M $NOME_ARQUIVO_ENVIO_G )
# LISTA_ARQUIVOS=( $NOME_ARQUIVO_ENVIO_M )
#echo "IM: $IM"
if [[ "$IM" == *PMG* ]]
then
LISTA_ARQUIVOS=( "${NOME_ARQUIVO_ENVIO_P}" "${NOME_ARQUIVO_ENVIO_M}" "${NOME_ARQUIVO_ENVIO_G}" )
elif [[ "$IM" == *PM* ]]
then
LISTA_ARQUIVOS=( "${NOME_ARQUIVO_ENVIO_P}" "${NOME_ARQUIVO_ENVIO_M}" )
elif [[ "$IM" == *G* ]]
then
LISTA_ARQUIVOS=( "${NOME_ARQUIVO_ENVIO_G}" )
elif [[ "$IM" == *P* ]]
then
LISTA_ARQUIVOS=( "${NOME_ARQUIVO_ENVIO_P}" )
elif [[ "$IM" == *M* ]]
then
LISTA_ARQUIVOS=( "${NOME_ARQUIVO_ENVIO_M}" )
else
echo "Error parametro tamanho imagem PMG nao foi encontrado"
exit 3
fi
#verifica se ja existe o dir, caso nao criar, se nao existe trocou!?
if [ ! -d "$NOME_DIR_NOVO" ]; then
echo "CRIANDO DIR NOVO $NOME_DIR_NOVO"
mkdir -p $NOME_DIR_NOVO
fi
TMP_JPG=${NOME_DIR_NOVO}${NOME_ARQUIVO_JPG_T}
for FILE in ${LISTA_ARQUIVOS[@]}; do
FILE_NAME=`echo ${FILE}|awk -F/ '{print $NF}'`
echo -e "\n Processando $FILE"
if [ -e "$FILE" ]; then
echo "JA EXISTE "
else
echo "Convertendo ${LINE} ${FILE}"
convert ${LINE} ${FILE}
fi
# echo "FILE = $FILE_NAME"
done # TERMINA LOOP DOS ARQUIVOS
if [ -z "$CURR_DIR" ]; then
echo "Primeiro dir $NOME_DIR_NOVO"
CURR_DIR=$NOME_DIR_NOVO
fi
# verifica se o DIR mudou caso sim pega o ultimo e envia tudo dentro dele
if [ "$CURR_DIR" == "$NOME_DIR_NOVO" ]; then
#ao fazer nada, ainda estamos no mesmo dir
echo ""
else
#ENVIA curr dir
NOME_DIR_S3=`echo $CURR_DIR |sed 's/\/mnt\/ConversaoMem\/Thumbnail//g'`
echo "Mudou dir verificando..."
echo $CURR_DIR $NOME_DIR_S3
#verifica se o dir tem arquivos .jpg a serem enviados
CMD_F="find ${CURR_DIR} -maxdepth 1 -type f -iname '*.jpg'|wc -l"
DIR_COUNT=`eval $CMD_F`
if [ "$DIR_COUNT" -ne "0" ]; then
#Grandes para outro BUCKET
if [[ "$IM" == *PMG* ]] || [[ "$IM" == *G* ]] || [[ "$IM" == *MG* ]] || [[ "$IM" == *PG* ]]
then
echo -e "\nEnviando: \n ${CURR_DIR}*_g.jpg \n s3://${BUCKET_G}${NOME_DIR_S3} \n"
enviasync "${CURR_DIR}*_g.jpg" "s3://${BUCKET_G}${NOME_DIR_S3}" "*_g.jpg"
fi
echo "Apagando _t.jpg e _g.jpg"
rm -fv ${CURR_DIR}*_g.jpg && rm -fv ${CURR_DIR}*_t.jpg
if [[ "$IM" == *PM* ]] || [[ "$IM" == *P* ]] || [[ "$IM" == *M* ]] || [[ "$IM" == *PMG* ]]
then
echo -e "\nEnviando $DIR_COUNT ao \n ${CURR_DIR}*.jpg AO s3://${BUCKET}${NOME_DIR_S3}"
enviasync "${CURR_DIR}*.jpg" "s3://${BUCKET}${NOME_DIR_S3}" "*.jpg"
fi
echo "Apagando ${CURR_DIR}*.jpg"
# rm -fv ${CURR_DIR}*.jpg
else
echo "Dir ta vazio"
fi
echo -e "\n\n\n ACERTOU DIR $CURR_DIR virou $NOME_DIR_NOVO \n\n\n"
CURR_DIR=$NOME_DIR_NOVO
fi
LISTA_ARQUIVOS=""
echo -e "\n[${I}/${TOTAL}] ........................................................ $(timer $t) \n "
I=`expr $I + 1`
done
printf 'Elapsed time: %s\n' $(timer $t)

Leave a Reply

Your email address will not be published. Required fields are marked *