summaryrefslogtreecommitdiff
path: root/tagomatic.py
diff options
context:
space:
mode:
authorjaseg <git@jaseg.net>2019-11-18 13:41:20 +0100
committerjaseg <git@jaseg.net>2019-11-18 13:41:20 +0100
commitbe3e6aca67951c9bcc138f867190c7aaf8894a60 (patch)
treecf302a00421c99027fdc70f525abda3869197395 /tagomatic.py
parent5b6396fbcd1cba7089e480ea45489ca6626448c1 (diff)
downloadkochbuch-be3e6aca67951c9bcc138f867190c7aaf8894a60.tar.gz
kochbuch-be3e6aca67951c9bcc138f867190c7aaf8894a60.tar.bz2
kochbuch-be3e6aca67951c9bcc138f867190c7aaf8894a60.zip
Generator basically working
Diffstat (limited to 'tagomatic.py')
-rw-r--r--tagomatic.py277
1 files changed, 203 insertions, 74 deletions
diff --git a/tagomatic.py b/tagomatic.py
index fce833a..b226355 100644
--- a/tagomatic.py
+++ b/tagomatic.py
@@ -13,8 +13,8 @@ import sqlite3
import tqdm
from PyQt5.QtWidgets import *
-from PyQt5.QtGui import QPixmap, QValidator
-from PyQt5.QtCore import QEvent, QItemSelectionModel, Qt
+from PyQt5.QtGui import QPixmap, QValidator, QTransform, QKeySequence
+from PyQt5.QtCore import Qt, QEvent, QItemSelectionModel, QCoreApplication, QSettings, QTimer
class TagValidator(QValidator):
def __init__(self, db):
@@ -22,11 +22,42 @@ class TagValidator(QValidator):
self.db = db
def validate(self, input, pos):
- if self.db.execute('SELECT id FROM tags WHERE name=?', (input,)).fetchone():
- return QValidator.Acceptable
- if self.db.execute('SELECT id FROM tags WHERE name LIKE ? + "%"', (input,)).fetchone():
- return QValidator.Intermediate
- return QValidator.Invalid
+ if self.db.execute('SELECT id FROM tags WHERE name=? LIMIT 1', (input,)).fetchone():
+ return QValidator.Acceptable, input, pos
+ return QValidator.Intermediate, input, pos
+
+class ZoomView(QGraphicsView):
+ def __init__(self, zoom=1.0, zoom_cb=lambda zoom: None):
+ super().__init__()
+ self.pixmap = None
+ self.zoom = zoom
+ self.zoom_cb = zoom_cb
+ self.setDragMode(QGraphicsView.ScrollHandDrag)
+ self.pixmap_item = QGraphicsPixmapItem()
+ self.scene = QGraphicsScene()
+ self.scene.addItem(self.pixmap_item)
+ self.setScene(self.scene)
+ self.setTransform(QTransform.fromScale(zoom, zoom))
+
+ def set_pic(self, le_path):
+ self.pixmap = QPixmap(le_path)
+ self.pixmap_item.setPixmap(self.pixmap)
+
+ def set_zoom(self, zoom):
+ self.zoom = zoom
+ self.setTransform(QTransform.fromScale(zoom, zoom))
+ self.zoom_cb(zoom)
+
+ def wheelEvent(self, evt):
+ if evt.modifiers() == Qt.ControlModifier:
+ self.set_zoom(max(1/16, min(4, self.zoom * (1.2**(evt.angleDelta().y() / 120)))))
+ elif evt.modifiers() == Qt.ShiftModifier:
+ QCoreApplication.sendEvent(self.horizontalScrollBar(), evt)
+ else:
+ if abs(evt.angleDelta().x()) > abs(evt.angleDelta().y()):
+ QCoreApplication.sendEvent(self.horizontalScrollBar(), evt)
+ else:
+ QCoreApplication.sendEvent(self.verticalScrollBar(), evt)
class App(QWidget):
def __init__(self, db, img_path):
@@ -37,42 +68,101 @@ class App(QWidget):
self.img_path = img_path
self.tag_validator = TagValidator(self.db)
self.tag_box = None
- self.pixmap = None
- self.zoom_levels = [ a*b for a in [0.01, 0.1, 1, 10, 100] for b in [0.25, 0.5, 1.0] ]
+ self.refresh_timer = QTimer(self)
+ self.refresh_timer.timeout.connect(self.refresh_pic)
+ self.refresh_timer.setSingleShot(True)
+ self.refresh_timer.setInterval(0)
+ self.tag_edits = {}
+ self.focused_tag_tid = None
+ self.settings = QSettings()
+
+ tags = [ tag for tag, in self.db.execute('SELECT name FROM tags') ]
+ self.new_completer = QCompleter(tags, self)
+ self.new_completer.setCompletionMode(QCompleter.InlineCompletion)
+ self.new_completer.setCaseSensitivity(Qt.CaseInsensitive)
pics = self.db.execute('SELECT filename, id FROM pics WHERE valid=1 ORDER BY filename').fetchall()
self.pid_for_fn = { k: v for k, v in pics }
self.fn_for_pid = { v: k for k, v in pics }
- _, self.current_pic = pics[0]
+ _, first_pid = pics[0]
+ self.current_pic = int(self.settings.value("current_picture", first_pid))
+ #self.shown = list(self.fn_for_pid.keys())
- pic_scroll = QScrollArea()
- self.pic_label = QLabel(self)
- pic_scroll.setWidget(self.pic_label)
- pic_scroll.setWidgetResizable(True)
- self.title_edit = QLineEdit()
+ zoom = float(self.settings.value('zoom', 1.0))
+ def save_zoom(zoom):
+ self.settings.setValue('zoom', zoom)
+ self.zoom_view = ZoomView(zoom, save_zoom)
self.pic_layout = QVBoxLayout()
- self.pic_layout.addWidget(pic_scroll)
+ self.pic_layout.addWidget(self.zoom_view)
self.refresh_pic()
self.pic_list = QListWidget()
self.pic_list.setSelectionMode(QAbstractItemView.SingleSelection)
- self.pic_list.itemActivated.connect(lambda item: self.set_pic(self.pid_for_fn[item.text()]))
+ show_item = lambda item: self.set_pic(self.pid_for_fn[item.text()])
+ self.pic_list.itemActivated.connect(show_item)
+ self.pic_list.itemClicked.connect(show_item)
for fn, _ in pics:
self.pic_list.addItem(fn)
- self.pic_list.setCurrentRow(0, QItemSelectionModel.SelectCurrent)
-
+ # Use row-based indexing since at this point there's no filters yet.
+ idx = next(i for i, (_fn, pid) in enumerate(pics) if pid == self.current_pic)
+ self.pic_list.setCurrentRow(idx, QItemSelectionModel.SelectCurrent)
+
+ nav_prev = QPushButton('Previous')
+ nav_prev.clicked.connect(self.prev)
+ nav_next = QPushButton('Next')
+ nav_next.clicked.connect(self.next)
+
+ shortcut1 = QShortcut(QKeySequence('Ctrl+N'), self)
+ shortcut1.activated.connect(self.next)
+ shortcut2 = QShortcut(QKeySequence('Ctrl+P'), self)
+ shortcut2.activated.connect(self.prev)
+ shortcut3 = QShortcut(QKeySequence(QKeySequence.MoveToNextPage), self)
+ shortcut3.activated.connect(self.next)
+ shortcut4 = QShortcut(QKeySequence(QKeySequence.MoveToPreviousPage), self)
+ shortcut4.activated.connect(self.prev)
+ shortcut4 = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Minus), self)
+ shortcut4.activated.connect(self.remove_current_tag)
+ #shortcut5 = QShortcut(QKeySequence(Qt.CTRL+ Qt.Key_Return), self)
+ #shortcut5.activated.connect(self.next)
+
+ nav_buttons = QHBoxLayout()
+ nav_buttons.addWidget(nav_prev)
+ nav_buttons.addWidget(nav_next)
+ list_layout = QVBoxLayout()
+ list_layout.addWidget(self.pic_list)
+ list_layout.addLayout(nav_buttons)
main_layout = QHBoxLayout()
- main_layout.addWidget(self.pic_list)
+ main_layout.addLayout(list_layout)
main_layout.addLayout(self.pic_layout)
self.setLayout(main_layout)
QApplication.instance().focusChanged.connect(self.focusChanged)
self.show()
+ def navigate(self, movement):
+ idx = self.pic_list.moveCursor(movement, Qt.NoModifier)
+ cmd = self.pic_list.selectionCommand(idx, None)
+ self.pic_list.selectionModel().setCurrentIndex(idx, cmd)
+ items = self.pic_list.selectedItems()
+ if len(items) != 1:
+ return
+ item, = items
+ self.set_pic(self.pid_for_fn[item.text()])
+
+ def prev(self):
+ self.navigate(QAbstractItemView.MovePrevious)
+
+ def next(self):
+ self.navigate(QAbstractItemView.MoveDown)
+
+ def schedule_refresh(self):
+ self.refresh_timer.start()
+
def set_pic(self, pid):
self.current_pic = pid
- self.refresh_pic()
+ self.settings.setValue("current_picture", pid)
+ self.schedule_refresh()
def refresh_list(self):
for i in range(self.pic_list.count()):
@@ -85,28 +175,33 @@ class App(QWidget):
def focusChanged(self, _old, new):
if new in self.tag_edits:
- print(f'Changed focus to tag {self.tag_edits[new]}')
- tid, name = self.tag_edits[new]
- self.focused_tag_name = name
-
- def remove_tag(self, tid):
- self.db.execute('DELETE FROM pic_tags WHERE id=?', (tid,))
+ tid, _lid = self.tag_edits[new]
+ self.focused_tag_tid = tid
+
+ def remove_current_tag(self):
+ obj = QApplication.instance().focusWidget()
+ if obj in self.tag_edits:
+ _tid, lid = self.tag_edits[obj]
+ self.remove_tag(lid)
+
+ def remove_tag(self, lid):
+ with self.db:
+ self.db.execute('DELETE FROM pic_tags WHERE id=?', (lid,))
self.refresh_pic()
- def update_tag(self, edit, tid):
- self.db.execute('UPDATE pic_tags SET value=? WHERE id=?', (edit.text(), tid))
+ def update_tag(self, edit, lid):
+ with self.db:
+ self.db.execute('UPDATE pic_tags SET value=? WHERE id=?', (edit.text(), lid))
def add_tag(self):
- self.db.execute('INESRT INTO pic_tags (pic, tag) VALUES (?, ?)', (self.current_pic,))
-
- def set_zoom(self, zoom):
- self.zoom = zoom
- self.pic_label.setPixmap(self.pixmap.scaled(
- self.pixmap.width()*zoom, self.pixmap.height()*zoom, Qt.KeepAspectRatio)
-
- def change_zoom(self, delta):
- idx = max(0, min(len(self.zoom_levels)-1, self.zoom_levels.index(self.zoom) + delta))
- self.set_zoom(self.zoom_levels[idx])
+ if not self.new_edit.hasAcceptableInput():
+ return
+ name = self.new_edit.text()
+ with self.db:
+ tid, = self.db.execute('SELECT id FROM tags WHERE name=?', (name,)).fetchone()
+ self.db.execute('INSERT INTO pic_tags (pic, tag) VALUES (?, ?)', (self.current_pic, tid))
+ self.focused_tag_tid = tid
+ self.refresh_pic()
def refresh_pic(self):
pid = self.current_pic
@@ -114,43 +209,65 @@ class App(QWidget):
self.img_path,
*self.db.execute('SELECT path FROM pics WHERE id=?', (pid,)).fetchone(),
self.fn_for_pid[pid])
- self.pixmap = QPixmap(le_path)
- print(path.isfile(le_path), le_path)
- self.set_zoom(self.zoom)
+ self.zoom_view.set_pic(le_path)
tag_layout = QGridLayout()
- tag_layout.addWidget(QLabel('Title'), 0, 1)
- tag_layout.addWidget(self.title_edit, 0, 2)
self.tag_edits = {}
- for i, (tid, name, value) in enumerate(self.db.execute('''SELECT pic_tags.id, tags.name, pic_tags.value FROM pic_tags
- JOIN tags ON pic_tags.tag = tags.id
+ tag_edit_for_tid = {}
+ for i, (lid, tid, tag_type, name, desc, value) in enumerate(self.db.execute(
+ '''SELECT pic_tags.id, tags.id, tags.type, name, description, value
+ FROM pic_tags JOIN tags ON pic_tags.tag = tags.id
WHERE pic_tags.pic = ?''',
(pid,)), start=1):
+ is_bool = tag_type == 'bool'
remove_btn = QPushButton('Remove')
- remove_btn.clicked.connect(lambda: self.remove_tag(tid))
- tag_edit = QLineEdit()
- self.tag_edits[tag_edit] = tid, name
- tag_edit.editingFinished.connect(lambda: self.update_tag(tag_edit, tid))
+ remove_btn.clicked.connect(lambda: self.remove_tag(lid))
tag_layout.addWidget(remove_btn, i, 0)
- tag_layout.addWidget(QLabel(f'{name}:'), i, 1)
- tag_layout.addWidget(tag_edit, i, 2)
+ tag_layout.addWidget(QLabel(f'{desc}:' if not is_bool else desc), i, 1)
+
+ if not is_bool:
+ tag_edit = QLineEdit()
+ self.tag_edits[tag_edit] = tid, lid
+ tag_edit_for_tid[tid] = tag_edit
+ tag_edit.setText(value)
+ tag_edit.editingFinished.connect(lambda: self.update_tag(tag_edit, lid))
+ tag_edit.returnPressed.connect(self.next)
+ tag_layout.addWidget(tag_edit, i, 2)
add_button = QPushButton('Add')
+ manage_button = QPushButton('Manage')
+ add_button.setEnabled(False)
add_button.clicked.connect(self.add_tag)
self.new_edit = QLineEdit()
- tag_layout.addWidget(QLabel('New:'), i+1, 0)
- tag_layout.addWidget(self.new_edit, i+1, 1)
- tag_layout.addWidget(add_button, i+1, 2)
+ self.new_edit.setCompleter(self.new_completer)
+ def edited(text):
+ ok = self.new_edit.hasAcceptableInput()
+ add_button.setEnabled(ok)
+ color = '#c4df9b' if ok else ('#fff79a' if text else '#ffffff')
+ self.new_edit.setStyleSheet(f'QLineEdit {{ background-color: {color} }}')
+ self.new_edit.textEdited.connect(edited)
+ new_layout = QHBoxLayout()
+ new_layout.addWidget(QLabel('New:'))
+ new_layout.addWidget(self.new_edit)
+ new_layout.addWidget(add_button)
+ new_layout.addWidget(manage_button)
self.new_edit.returnPressed.connect(self.add_tag)
self.new_edit.setValidator(self.tag_validator)
if self.tag_box:
self.pic_layout.removeWidget(self.tag_box)
+ self.tag_box.close()
self.tag_box = QGroupBox('Image tags')
- self.tag_box.setLayout(tag_layout)
+ self.tag_box.setAttribute(Qt.WA_DeleteOnClose, True)
+ box_layout = QVBoxLayout()
+ box_layout.addLayout(tag_layout)
+ box_layout.addLayout(new_layout)
+ self.tag_box.setLayout(box_layout)
self.pic_layout.addWidget(self.tag_box)
- self.update()
+
+ if self.focused_tag_tid in tag_edit_for_tid:
+ tag_edit_for_tid[self.focused_tag_tid].setFocus()
if __name__ == '__main__':
import argparse
@@ -163,7 +280,6 @@ if __name__ == '__main__':
db = sqlite3.connect(args.database)
db.execute('PRAGMA foreign_keys = ON')
- print(f'SQLite version {sqlite3.version}')
db_initialized = bool(db.execute('SELECT name FROM sqlite_master WHERE type="table" AND name="pics"').fetchone())
if not db_initialized:
@@ -177,6 +293,8 @@ if __name__ == '__main__':
cur.execute('''CREATE TABLE IF NOT EXISTS pics (
id INTEGER PRIMARY KEY,
filename TEXT,
+ category TEXT,
+ book TEXT,
prefix TEXT,
sha3 TEXT UNIQUE,
pgnum INTEGER,
@@ -196,19 +314,16 @@ if __name__ == '__main__':
value TEXT)''')
cur.execute('''INSERT INTO tags (type, name, description)
- VALUES ("str", "dir", "Scan Directory")
- ON CONFLICT DO NOTHING''')
- cur.execute('''INSERT INTO tags (type, name, description)
- VALUES ("str", "title", "Image Title")
+ VALUES ("str", "title", "Image Title"),
+ ("str", "comment", "Comment"),
+ ("bool", "check", "Double-check this image")
ON CONFLICT DO NOTHING''')
print('Enumerating pages')
piclist = glob.glob(path.join(glob.escape(args.img_path), '**/*.jpg'), recursive=True)
- print(' ', len(piclist), path.join(glob.escape(args.img_path), '**/*.jpg'))
print('Reading pages')
cur.execute('UPDATE pics SET valid=0')
- cur.execute('DELETE FROM pic_tags WHERE tag=(SELECT id FROM tags WHERE name="dir")')
for pic in tqdm.tqdm(piclist):
relpath = path.relpath(pic, args.img_path)
@@ -227,19 +342,30 @@ if __name__ == '__main__':
prefixes[prefix].append((int(series), int(imgnum), hash))
+ book, *rest = path.normpath(rel).split(os.sep)
+ if rest:
+ category, *rest = rest
+ if rest:
+ raise UserWarning(f'Unhandled directory component "{rest}"')
+ else:
+ category = None
+
# upsert hack
- cur.execute('UPDATE pics SET filename=?, path=?, prefix=?, valid=1 WHERE sha3=?',
- (fn, rel, prefix, hash))
+ cur.execute('UPDATE pics SET book=?, category=?, filename=?, path=?, prefix=?, valid=1 WHERE sha3=?',
+ (book, category, fn, rel, prefix, hash))
if cur.rowcount == 0:
- cur.execute('INSERT INTO pics (filename, path, prefix, sha3, valid) VALUES (?, ?, ?, ?, 1)',
- (fn, rel, prefix, hash))
+ cur.execute('INSERT INTO pics (book, category, filename, path, prefix, sha3, valid) VALUES (?, ?, ?, ?, ?, ?, 1)',
+ (book, category, fn, rel, prefix, hash))
+
+ title_tag_exists = bool(db.execute('''SELECT pic_tags.id FROM pic_tags
+ JOIN pics ON pics.id=pic_tags.pic, tags ON tags.id=pic_tags.tag
+ WHERE tags.name="title" AND pics.sha3=?''', (hash,)).fetchone())
- for tag in path.normpath(rel).split(os.sep):
- cur.execute('''INSERT INTO pic_tags (pic, tag, value) VALUES (
- (SELECT id FROM pics WHERE sha3=?),
- (SELECT id FROM tags WHERE name="dir"),
- ?)''',
- (hash, tag))
+ if not title_tag_exists:
+ db.execute('''INSERT INTO pic_tags (pic, tag) VALUES (
+ (SELECT id FROM pics WHERE sha3=?),
+ (SELECT id FROM tags WHERE name="title"))''',
+ (hash,))
print('Reindexing pages')
for entries in prefixes.values():
@@ -253,6 +379,9 @@ if __name__ == '__main__':
signal.signal(signal.SIGINT, signal.SIG_DFL)
+ QCoreApplication.setOrganizationName('jaseg')
+ QCoreApplication.setOrganizationDomain('jaseg.net')
+ QCoreApplication.setApplicationName('Tag-O-Matic')
app = QApplication(sys.argv[:1] + unparsed)
ex = App(db, args.img_path)
sys.exit(app.exec_())