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)