Skip to content

Instantly share code, notes, and snippets.

@Bruno02468
Last active September 24, 2021 03:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Bruno02468/57f57a6b4b5462cf668a3931e0cb7f3a to your computer and use it in GitHub Desktop.
Save Bruno02468/57f57a6b4b5462cf668a3931e0cb7f3a to your computer and use it in GitHub Desktop.
Adobe OCR modification time fix
#!/usr/bin/env python3
# Bruno Borges Paschoalinoto - Nenhum direito reservado
#
# Scriptzinho para usar com o OCR do Adobe Acrobat, porque este descarta as
# datas de modificação (mtime) e acesso (atime) quando ele cria as versões com
# OCR. Este programa separa arquivos originais e OCR-zados (usando o recurso de
# sufixo e prefixo do Adobe) e devolve o mtime e atime originais.
#
# Além disso, ele opcionalmente move os originais para uma pasta separada, e
# renomeia os OCR-zados para terem os mesmos nomes dos originais.
#
# Na dúvida, invoque com -h para mais informações.
import argparse
import os
import pathlib
import shutil
import sys
from os import DirEntry, PathLike
from copy import deepcopy
from typing import Optional, List, Dict, Tuple
# tipo comumente usado -- lista de pares de diretórios, sendo o primeiro o
# "original", e o segundo o modificado
Pairs = List[Tuple[DirEntry, DirEntry]]
# mensagens para evitar frunfa, e caso queiramos internacionalizar o programa.
LANG: str = "PT_BR"
MESSAGES_PT_BR: Dict[str, str] = {
"pfx_sfx_null": "Tanto sufixo quanto prefixo são nulos!",
"dest_not_dir": "Caminho de destino não existe ou não é uma pasta!",
"help_prog": "Conserta ctime e renomeia arquivos pós OCR do Acrobat.",
"epilog": "Você *deve* especificar pelo menos um sufixo ou um prefixo!",
"help_pfx": "Prefixo inserido pelo Acrobat.",
"help_sfx": "Sufixo inserido pelo Acrobat.",
"help_dir": "Pasta onde se encontram os arquivos (assume-se pasta atual).",
"help_move_to": (
"Pasta para mover os arquivos originais (opcional). Ela será criada caso "
"ainda não exista."
),
"dir_not_dir": "Pasta de origem não existe ou não é pasta!",
"pairs_follow": "Arquivos a serem processados:",
"no_pairs": "Não encontrei nenhum par de arquivos...",
"dir_and_move_to_conflict": (
"A pasta de destino dos originais não pode ser a mesma pasta dos arquivos "
"iniciais!"
),
"done": "Pronto."
}
LANGS_MESSAGES: Dict[str, Dict[str, str]] = {
"PT_BR": MESSAGES_PT_BR
}
MESSAGES = LANGS_MESSAGES[LANG]
# essa função tenta remover prefixo e sufixos de um nome, e retorna o nome base
# ou None caso não seja possível
def separate(
filename: str, prefix: Optional[str], suffix: Optional[str]
) -> Optional[str]:
# assegurar que temos pelo menos um dos dois
if prefix is None and suffix is None:
raise ValueError(MESSAGES["pfx_sfx_null"])
# testar prefixo
if prefix:
if filename.startswith(prefix):
filename = filename[len(prefix):]
else:
return None
# testar sufixo
if suffix:
if filename.endswith(suffix):
filename = filename[:-len(suffix)]
else:
return None
return filename
# acha pares de arquivos tipo "nome.ext" e "prefixo-nome-sufixo.pdf"
# note que ext não necessariamente é pdf, e.g. um .tif pode virar .pdf
# recebe um caminho de diretório, retorna uma lista de pares de nomes
def find_pairs(
dirname: PathLike, prefix: Optional[str], suffix: Optional[str]
) -> Pairs:
# começamos listando todos os arquivos na pasta...
all_files = os.scandir(dirname)
# vamos separar os arquivos entre "estão de acordo com prefixo e sufixo" e os
# que não aderem ao padrão... vamos indexá-los pelo "nome base": o nome sem
# extensão nem prefixos ou sufixos.
organized: Dict[str, Tuple[Optional[DirEntry], Optional[DirEntry]]] = {}
for f in filter(lambda di: di.is_file(), all_files):
# ver se é um arquivo original ou modificado
fn, ext = os.path.splitext(f.name)
sep = separate(fn, prefix, suffix)
key = sep or fn
# garantir que haja a key no dicionário
if key not in organized:
organized[key] = (None, None)
orig, modif = organized[key]
# fazer a modificação com o arqivo que achamos
if sep:
modif = f
else:
orig = f
# mandar de volta pro dicionário
organized[key] = (orig, modif)
# agora vamos filtrar o dicionário para apenas arquivos que tiveram tanto
# uma versão "original" e uma versão com prefixo e/ou sufixo, e construir
# a lista para ser retornada
rl: Pairs = []
for k, (orig, modif) in organized.items():
if orig is not None and modif is not None:
rl.append((orig, modif))
return rl
# essa função, munida dos pares de arquivos, muda as datas de modificação dos
# arquivos modificados para baterem com os originais, e também move os
# originais para uma subpasta designada (opcional).
# caso sejam movidos os originais, os modificados são renomeados para terem o
# mesmo nome dos originais.
def apply_changes(pairs: Pairs, move_to: Optional[PathLike]):
# checar pra ver se a move_to é uma pasta
if move_to and not os.path.isdir(move_to):
raise ValueError(MESSAGES["dest_not_dir"])
orig: DirEntry
modif: DirEntry
for orig, modif in pairs:
mt = os.path.getmtime(orig.path)
at = os.path.getatime(orig.path)
# setar datas de acesso e modificação
os.utime(modif.path, times=(at, mt))
if move_to:
# mover o original
op = deepcopy(orig.path)
shutil.move(orig.path, move_to)
os.sync()
# renomear o novo para bater com original
os.rename(modif.path, op)
os.sync()
# função principal do programa: processa argumentos do shell, aplica mudanças,
# e reporta sucesso/problemas. retorna exit code.
def main(argv: List[str]) -> int:
# criar um parser dos argumentos
parser = argparse.ArgumentParser(
description=MESSAGES["help_prog"], add_help=True, epilog=MESSAGES["epilog"]
)
parser.add_argument(
"-p", "--prefix", type=str, default=None, help=MESSAGES["help_pfx"]
)
parser.add_argument(
"-s", "--suffix", type=str, default=None, help=MESSAGES["help_sfx"]
)
parser.add_argument(
"-m", "--move-to", type=str, help=MESSAGES["help_move_to"]
)
parser.add_argument(
"dir", type=str, default=".", help=MESSAGES["help_dir"]
)
args = parser.parse_args(argv[1:])
# assegurar que temos pelo menos um prefixo ou um sufixo (ou ambos)
prefix: Optional[str] = args.prefix
suffix: Optional[str] = args.suffix
if prefix is None and suffix is None:
print(MESSAGES["pfx_sfx_null"])
parser.print_usage()
return -1
# criar a pasta de move_to, caso não exista ainda
move_to: Optional[str] = args.move_to
if move_to:
# primeiro vamos ver se ela é igual à dir
if os.path.exists(move_to):
if os.path.samefile(args.dir, move_to):
print(MESSAGES["dir_and_move_to_conflict"])
return -1
# isso é tipo um mkdir -p
pathlib.Path(move_to).mkdir(parents=True, exist_ok=True)
# assegurar que dir é uma pasta
if not os.path.isdir(args.dir):
print(MESSAGES["dir_not_dir"])
parser.print_usage()
return -1
# localizar os pares de arquivos
pairs = find_pairs(args.dir, prefix, suffix)
# certificar que há pelo menos um par
if len(pairs) == 0:
print(MESSAGES["no_pairs"])
return 0
# printar os pares
print(MESSAGES["pairs_follow"])
for (orig, modif) in pairs:
print(f" {modif.name} -> {orig.name}")
# aplicar as mudanças aos pares
apply_changes(pairs, move_to)
# pronto
print(MESSAGES["done"])
return 0
# agora, caso esse script esteja sendo invocado diretamente, vamos extrair o
# argv e transferir o controle para a main
if __name__ == "__main__":
sys.exit(main(sys.argv))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment