Meu gerenciador de arquivos é o ranger. Ele é de terminal, permite remapear todos os comandos me permitindo ser mais eficiente em navegar pelos meus arquivos, e é incrivelmente extensível já que permite a criação de comandos customizados em python. Se isso não bastasse, ele também tem um monte de outras funcionalidades (visualização de arquivos extensível, abas, rótulos, ...). Vá checar a página do GitHub deles, sério.
Uma funcionalidade muito útil do ranger é o comando bulkrename
. Ele permite
que você abra um editor com o nome de todos os arquivos selecionados e os edite.
Depois de salvar, um script de shell é gerado para realizar as renomeações
necessárias (e te dá a chance de revisá-lo), e como se fosse mágica *puff*
você acabou de renomear um monte de arquivos simultaneamente da conveniência do
seu editor de texto preferido.
Veja um exemplo:
Bem conveniente. Mas se você parar para pensar, é bem limitado. Por que permitir só renomeação? O mesmo funcionamento poderia ser usado para editar qualquer atributo do arquivo. Só é necessário fornecer uma forma de obter o atributo para cada arquivo selecionado e de gerar um comando de shell que mude o atributo para aplicar a mudança feita pelo usuário.
Recentemente eu quis modificar o rótulo ID3 Artista de múltiplos arquivos mp3
ao mesmo tempo o que me motivou a escrever a versão genérica do bulkrename
do ranger: bulk
.
Para isso eu basicamente copiei o código do bulkrename
e generalizei a
obtenção do atributo do arquivo e a geração do comando de modificação do
atributo, chamando get_attribute()
e get_change_attribute_cmd()
,
respectivamente, de um subcomando bulk armazenado no dicionário bulk
.
A classe do comando bulk
é a seguinte:
class bulk(Command): """:bulk <attribute> This command opens a list with the attribute <attribute> for each of the selected files in an external editor. After you edit and save the file, it will generate a shell script which changes the attributes in bulk according to the changes you did in the file. This shell script is opened in an editor for you to review. After you close it, it will be executed. """ bulk = {} def execute(self): # pylint: disable=too-many-locals,too-many-statements import sys import tempfile from ranger.container.file import File from ranger.ext.shell_escape import shell_escape as esc py3 = sys.version_info[0] >= 3 # get bulk command argument bkname = self.rest(1) # Create and edit the file list files = [f for f in self.fm.thistab.get_selection()] attributes = [self.bulk[bkname].get_attribute(f) for f in files] listfile = tempfile.NamedTemporaryFile(delete=False) listpath = listfile.name if py3: listfile.write("\n".join(attributes).encode("utf-8")) else: listfile.write("\n".join(attributes)) listfile.close() self.fm.execute_file([File(listpath)], app='editor') listfile = open(listpath, 'r') new_attributes = listfile.read().split("\n") listfile.close() os.unlink(listpath) if all(a == b for a, b in zip(attributes, new_attributes)): self.fm.notify("Nothing to be done!") return print(new_attributes) # Generate script cmdfile = tempfile.NamedTemporaryFile() script_lines = [] script_lines.append("# This file will be executed when you close the editor.\n") script_lines.append("# Please double-check everything, clear the file to abort.\n") script_lines.extend("%s\n" % self.bulk[bkname].get_change_attribute_cmd(file, old, new) for old, new, file in zip(attributes, new_attributes, files) if old != new) script_content = "".join(script_lines) if py3: cmdfile.write(script_content.encode("utf-8")) else: cmdfile.write(script_content) cmdfile.flush() # Open the script and let the user review it self.fm.execute_file([File(cmdfile.name)], app='editor') cmdfile.seek(0) # Do the attribute changing self.fm.run(['/bin/sh', cmdfile.name], flags='w') cmdfile.close()
Então, dentro dessa classe, eu adicionei uma classe para cada um dos subcomandos
bulk que eu queria adicionar: id3art
, id3tit
e id3alb
, que modificam
o rótulo ID3 para o Artista, Título e Álbum, respectivamente.
Para cada um deles, eu implementei os métodos get_attribute()
e
get_change_attribute_cmd()
. O método get_attribute()
recebe o objeto de
arquivo do ranger e deve retornar uma string com o atributo (no caso do
id3art
, o rótulo ID3 Artista, o qual foi obtido usando o módulo python
eyed3
). O método get_change_attribute_cmd()
recebe o objeto de arquivo
do ranger, o atributo antigo (o retornado por get_attribute()
) e o novo (o
valor editado pelo usuário), e deve retornar uma string contendo o comando de
shell para aplicar a mudança feita pelo usuário (no caso do id3art
, eyeD3
-a NovoArtista nomeDoArquivo.mp3
).
Finalmente, eu também adicionei uma entrada para cada um desses subcomandos no
dicionário bulk
, que mapeia o nome do subcomando ao seu objeto.
Traduzindo tudo isso para código, foi isso o que eu adicionei dentro da classe
bulk
:
class id3art(object): def get_attribute(self, file): import eyed3 artist = eyed3.load(file.relative_path).tag.artist return str(artist) if artist else '' def get_change_attribute_cmd(self, file, old, new): from ranger.ext.shell_escape import shell_escape as esc return "eyeD3 -a %s %s" % (esc(new), esc(file)) class id3tit(object): def get_attribute(self, file): import eyed3 title = eyed3.load(file.relative_path).tag.title return str(title) if title else '' def get_change_attribute_cmd(self, file, old, new): from ranger.ext.shell_escape import shell_escape as esc return "eyeD3 -t %s %s" % (esc(new), esc(file)) class id3alb(object): def get_attribute(self, file): import eyed3 album = eyed3.load(file.relative_path).tag.album return str(album) if album else '' def get_change_attribute_cmd(self, file, old, new): from ranger.ext.shell_escape import shell_escape as esc return "eyeD3 -A %s %s" % (esc(new), esc(file)) bulk = {'id3art': id3art(), 'id3tit': id3tit(), 'id3alb': id3alb(), }
E aqui está ele em ação:
Já que eu percebi que esse funcionamento genérico do comando bulk poderia ser
útil para todos os usuários do ranger, cada um implementando seu próprio
subcomando bulk, eu sugeri a adição da funcionalidade. Aparentemente a ideia
foi bem aceita, e inclusive há a intenção de tornar o bulkrename
apenas um
apelido para um subcomando bulk, então talvez em um futuro próximo você possa
criar seu próprio subcomando bulk usando o comando bulk
já integrado ao
ranger 🙂.