Last active
September 24, 2021 03:24
-
-
Save Bruno02468/57f57a6b4b5462cf668a3931e0cb7f3a to your computer and use it in GitHub Desktop.
Adobe OCR modification time fix
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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