Last active
January 28, 2016 07:36
-
-
Save isoraqathedh/c5fda595057f30fb3117 to your computer and use it in GitHub Desktop.
Scan processing system ver. 5
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
(provide 'boc-proc) | |
(defvar boc-proc/default-tags | |
'(("type" . "boc") | |
"title" | |
("book" . "auto") | |
("page". "auto") | |
("subpage" . "auto") | |
"category-code" | |
"extra-tags") | |
"List of ini tags to be specified for each individual file.") | |
(defvar boc-proc/directory | |
"~/Downloads/Scan inputs" | |
"Location of where the scans are.") | |
(defvar boc-proc/target-file | |
"~/Documents/Autohotkey/scan-params.ini" | |
"Location of the file that will eventually get written into.") | |
(defun boc-proc/insert-ini-section (file-name) | |
"Formats an ini section for each individual file." | |
(insert (format "[%s%s]\n" ; Input file | |
(getenv "HOME") | |
(subseq (replace-regexp-in-string "/" "\\\\" file-name) 1))) | |
(dolist (tag boc-proc/default-tags) | |
(insert (if (consp tag) | |
(format "%s = %s\n" (car tag) (cdr tag)) ; tag with default | |
(format "%s = \n" tag)))) ; tag without default | |
(insert-char ?\n)) ; ending newline | |
(defun boc-proc/prep-ini-file () | |
"Writes the skeleton for the ini file for each file to process" | |
(interactive) | |
(save-current-buffer | |
(find-file boc-proc/target-file) | |
(insert-file-contents "~/.emacs.d/mine/boc-proc-template.ini") | |
(dolist (file-entry (file-expand-wildcards | |
(format "%s/*.jpg" boc-proc/directory))) | |
(boc-proc/insert-ini-section file-entry)) | |
(not-modified))) | |
(define-my-key "C-c b" 'boc-proc/prep-ini-file) |
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/python | |
# | |
# Book of Conworlds Scan Processing System | |
# Version 5.O.1 | |
# Now in Python | |
import configparser, os, exiftool, glob, time | |
# Read the config files. | |
# | |
# process_parameters is an actual configuration file | |
# that controls how the thing works. | |
# | |
# files_to_process however is an ini file | |
# that is actually a script to control how to tag and rename each fie. | |
config_file_location = os.path.expanduser('~/Documents/AutoHotkey/scan.ini') | |
script_file_location = os.path.expanduser( | |
'~/Documents/AutoHotkey/scan-params.ini') | |
process_parameters = configparser.RawConfigParser() | |
process_parameters.optionxform = lambda option:option # Make it case sensitive | |
process_parameters.read(config_file_location, encoding='utf-16') | |
files_to_process = configparser.ConfigParser() | |
files_to_process.read(script_file_location) | |
path_out = process_parameters.get("scan2", "pathout").replace("\\", "/")[:-1] | |
boc_folder = '{}/Book of Conworlds'.format(path_out) | |
# Basic constants | |
genre_name_transform = {'languages': "Langs", | |
'planets': "Worlds", | |
'': "Mixed/Other"} | |
combined_codes = (dict(list(process_parameters['languages'].items()) + | |
list(process_parameters['planets'].items()) + | |
list(process_parameters['extra-tags'].items()))) | |
timestamp_format = "%Y-%m (%b)" | |
# Error classes | |
class CaseFallthroughException(Exception): pass | |
# Utility functions | |
def file_or_folder_exist_p(glob_spec): | |
if glob.glob(glob_spec): return True | |
else: return False | |
def defaultp(thing): | |
return None if thing in ("auto", "", None) else thing | |
def keyornone(section, key): | |
return section[key] if key in section else None | |
# Functions related to tags | |
def split_or_empty(comma_separated_string): | |
return ([] if comma_separated_string == "" | |
else comma_separated_string.split(', ')) | |
def get_categories(section): | |
return split_or_empty(section['category-code']) | |
def categorycode(category_list, genre): | |
if len(category_list) == 1: | |
category_code = category_list[0] | |
elif genre == 'planets': category_code = 'Tpl' | |
elif genre == 'languages': category_code = 'Trl' | |
else: category_code = 'Mis' | |
return category_code | |
def get_genre(section): | |
def all_match_category_p(category_bag, match_category): | |
return all(cat in process_parameters[match_category] | |
for cat in category_bag) | |
categories = get_categories(section) | |
for genres in ('languages', 'planets'): | |
if all_match_category_p(categories, genres): genre = genres; break | |
else: genre = '' | |
return genre | |
def get_full_category_names(section): | |
category_short_codes = get_categories(section) | |
genre = get_genre(section) | |
return [combined_codes[code] for code in category_short_codes] | |
def get_other_tags(section): | |
return [process_parameters['extra-tags'][code] | |
for code in split_or_empty(section['extra-tags'])] | |
def form_exiftool_args(old_filename, title, categories, overwrite=True): | |
final_form = [] | |
final_form.append('-Title=' + title) | |
for category in categories: | |
final_form.append('-Subject+=' + category) | |
if overwrite: | |
final_form.append('-overwrite_original_in_place') | |
final_form.append(old_filename) | |
return final_form | |
# Classes for each type | |
class BocBook: | |
number = 1 | |
path_syntax = "{}/Book {}" | |
def syncpath(self): | |
self.path = self.path_syntax.format(boc_folder, self.number) | |
def _setbook(self, book_number): | |
self.number = book_number | |
self.syncpath() | |
return self | |
def __init__(self, book_number=1): | |
self.setbook(book_number) | |
def bookexists(self): return file_or_folder_exist_p(self.path) | |
def bookignored(self): return (str(self.number) | |
in process_parameters['skipbooks']) | |
def setbook(self, book_number=None): | |
if book_number: self._setbook(book_number) | |
else: | |
test_number = 1 | |
while self.bookexists() or self.bookignored(): | |
test_number += 1 | |
self._setbook(test_number) | |
else: self._setbook(test_number - 1) | |
return self | |
class BocPurpleBook(BocBook): | |
path_syntax = "{}/Book p{}" | |
def ignored(self): return ('p{}'.format(self.number) | |
in process_parameters['skipbooks']) | |
class BocPage(BocBook): | |
page = 1 | |
subpage = 'a' | |
page_syntax = "{:>02}" | |
subpage_syntax = "{}" | |
def glob_param(self): | |
return (self.path + "/" + | |
self.page_syntax + self.subpage_syntax + "-*.jpg") | |
def pageexists(self): | |
return file_or_folder_exist_p( | |
self.glob_param().format(self.page, self.subpage)) | |
def pageignored(self): | |
return (self.page_syntax.format(self.page) | |
in process_parameters.get('skippages', str(self.number), | |
fallback="")) | |
def _setpage(self, page_number): | |
self.page = page_number | |
return self | |
def _setsubpage(self, subpage_letter): | |
self.subpage = subpage_letter | |
return self | |
def setpage(self, page_number=None, subpage_letter=None): | |
if page_number: self._setpage(page_number) | |
else: | |
# page loop | |
for test_page in range(1, 100): | |
self._setpage(test_page) | |
if not (self.pageexists() or self.pageignored()): break | |
# subpage loop | |
if subpage_letter: self._setsubpage(subpage_letter) | |
else: | |
for test_subpage in "abcdefghijklmnopqrstuvwxyz": | |
self._setsubpage(test_subpage) | |
if not self.pageexists(): break | |
return self | |
def autoname(self, title="Untitled", book=None, page=None, subpage=None, | |
categories=[], genre=None): | |
# Find the appropriate page number: | |
self.setbook(defaultp(book)) | |
self.setpage(defaultp(page), defaultp(subpage)) | |
# Find the category code: | |
category_code = categorycode(categories, genre) | |
# Stitch the name together: | |
return ((self.path + "/" + self.page_syntax | |
+ self.subpage_syntax + "-{}-{}.jpg") | |
.format(self.page, self.subpage, category_code, title)) | |
class BocPurplePage(BocPurpleBook, BocPage): pass | |
class BocNonbookPage(BocPage): | |
path_syntax = "{}/Non-BoC/{}" | |
def syncpath(self): | |
self.path = self.path_syntax.format( | |
boc_folder, time.strftime(timestamp_format)) | |
def autoname(self, title="Untitled", page=None, categories=[], genre=None): | |
self.setpage(defaultp(page)) # Find the page number | |
category_code = categorycode(categories, genre) # Find the category code | |
# Stitch the name together: | |
return ((self.path + "/" + self.page_syntax | |
+ self.subpage_syntax + "-{}-{}.jpg") | |
.format(self.page, self.subpage, category_code, title)) | |
class NonBocPage(BocPage): | |
path_syntax = "{}/Unsorted by Date/{}" | |
page_syntax = "SCAN{:>04}.jpg" | |
def syncpath(self): | |
self.path = self.path_syntax.format(boc_folder, | |
time.strftime(timestamp_format)) | |
def pageexists(self): | |
return file_or_folder_exist_p( | |
(self.path + "/" + self.page_syntax).format(self.page)) | |
def setpage(self, page=None): | |
if page: self._setpage(page) | |
else: | |
for test_page in range(10000): | |
self._setpage(test_page) | |
if not self.pageexists(): break | |
return self | |
def autoname(self): | |
self.setpage() # Find the page number | |
# Stitch the name together: | |
return (self.path + "/" + self.page_syntax).format(self.page) | |
class TheDen(): | |
path = os.path.expanduser("~/Documents/Junk/Xn/Pre") | |
def autoname(self, old_filename): | |
return self.path + "/" + os.path.split(old_filename)[1] | |
## Functions that automatically determine the filename | |
def getname(name_type, **kwargs): | |
"""Automatically calculates the output filename for all file types, | |
largely smoothing over the differences that the classes above makes.""" | |
def fromkwargs(*keys): | |
return [kwargs[key] for key in keys] | |
if name_type == "den": | |
page_outname = TheDen().autoname(*fromkwargs('original_file_name')) | |
elif name_type == "boc": | |
page_outname = (BocPage().autoname(*fromkwargs( | |
'title', 'book', 'page', 'subpage', 'categories', 'genre'))) | |
elif name_type == "purple": | |
page_outname = (BocPurplePage().autoname(*fromkwargs( | |
'title', 'book', 'page', 'subpage', 'categories', 'genre'))) | |
elif name_type == "nboc": | |
page_outname = (BocNonbookPage().autoname(*fromkwargs( | |
'title', 'page', 'categories', 'genre'))) | |
elif name_type == "uncategorised": | |
page_outname = NonBocPage().autoname() | |
else: raise CaseFallthroughException() | |
return page_outname | |
# Executed on script run: | |
with exiftool.ExifTool() as et: | |
# Execute the script INI file | |
for (file_to_move, file_section) in files_to_process.items(): | |
if file_section[0] in ("DEFAULT", "general"): | |
# Skip the two non-file sections in the script INI file | |
continue | |
# Otherwise, read in the commands for the next file | |
if file_section['type'] in ('boc', 'purple', 'nboc'): | |
# All three of these use the same mechanism, so they are coalesced | |
# Store these because they get used more than once | |
genre = get_genre(file_section) | |
categories = get_categories(file_section) | |
long_categories = get_full_category_names(file_section) | |
# Standard EXIF tagging then moving | |
et.execute(*[bytes(arg, encoding='utf-8') for arg in | |
form_exiftool_args( | |
file_section.name, file_section['title'], | |
long_categories + [genre_name_transform[genre]] | |
+ get_other_tags(file_section))]) | |
os.renames(file_to_move, | |
getname(file_section['type'], | |
title=keyornone(file_section, 'title'), | |
genre=genre, | |
categories=categories, | |
book=keyornone(file_section, 'book'), | |
page=keyornone(file_section,'page'), | |
subpage=keyornone(file_section,'subpage'))) | |
elif file_section['type'] == 'uncategorised': | |
os.renames(file_to_move, getname("uncategorised")) | |
elif file_section['type'] == 'den': | |
# Move the file into The Den | |
os.rename(file_to_move, | |
getname("den", original_file_name=file_to_move)) | |
else: raise CaseFallthroughException() | |
# Clear out the contents of the script file afterward | |
with open(script_file_location, 'w') as f: pass |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment