Skip to content

Instantly share code, notes, and snippets.

@isoraqathedh
Last active January 28, 2016 07:36
Show Gist options
  • Save isoraqathedh/c5fda595057f30fb3117 to your computer and use it in GitHub Desktop.
Save isoraqathedh/c5fda595057f30fb3117 to your computer and use it in GitHub Desktop.
Scan processing system ver. 5
(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)
#!/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