Skip to content

Commit ade1fb1

Browse files
committed
Merge remote-tracking branch 'tags/main' into image-preview
2 parents 955d4a0 + 31f4022 commit ade1fb1

File tree

3 files changed

+167
-100
lines changed

3 files changed

+167
-100
lines changed

tagstudio/src/core/library.py

Lines changed: 114 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,9 @@ def __init__(self) -> None:
323323
# That filename can then be used to provide quick lookup to image metadata entries in the Library.
324324
# NOTE: On Windows, these strings are always lowercase.
325325
self.filename_to_entry_id_map: dict[str, int] = {}
326+
# A list of file extensions to be ignored by TagStudio.
327+
self.default_ext_blacklist: list = ['json', 'xmp', 'aae']
328+
self.ignored_extensions: list = self.default_ext_blacklist
326329

327330
# Tags =================================================================
328331
# List of every Tag object (ts-v8).
@@ -612,6 +615,10 @@ def open_library(self, path: str) -> int:
612615
self.verify_ts_folders()
613616
major, minor, patch = json_dump['ts-version'].split('.')
614617

618+
# Load Extension Blacklist ---------------------------------
619+
if 'ignored_extensions' in json_dump.keys():
620+
self.ignored_extensions = json_dump['ignored_extensions']
621+
615622
# Parse Tags ---------------------------------------------------
616623
if 'tags' in json_dump.keys():
617624
start_time = time.time()
@@ -850,6 +857,7 @@ def to_json(self):
850857
Used in saving the library to disk.
851858
"""
852859
file_to_save = {"ts-version": ts_core.VERSION,
860+
"ignored_extensions": [],
853861
"tags": [],
854862
"collations": [],
855863
"fields": [],
@@ -858,6 +866,9 @@ def to_json(self):
858866
}
859867

860868
print('[LIBRARY] Formatting Tags to JSON...')
869+
870+
file_to_save['ignored_extensions'] = [i for i in self.ignored_extensions if i is not '']
871+
861872
for tag in self.tags:
862873
file_to_save["tags"].append(tag.compressed_dict())
863874

@@ -925,6 +936,7 @@ def clear_internal_vars(self):
925936
self.missing_files.clear()
926937
self.fixed_files.clear()
927938
self.filename_to_entry_id_map: dict[str, int] = {}
939+
self.ignored_extensions = self.default_ext_blacklist
928940

929941
self.tags.clear()
930942
self._next_tag_id: int = 1000
@@ -950,7 +962,7 @@ def refresh_dir(self):
950962
# p = Path(os.path.normpath(f))
951963
if ('$RECYCLE.BIN' not in f and ts_core.TS_FOLDER_NAME not in f
952964
and 'tagstudio_thumbs' not in f and not os.path.isdir(f)):
953-
if os.path.splitext(f)[1][1:].lower() in ts_core.ALL_FILE_TYPES:
965+
if os.path.splitext(f)[1][1:].lower() not in self.ignored_extensions:
954966
self.dir_file_count += 1
955967
file = str(os.path.relpath(f, self.library_dir))
956968

@@ -1416,102 +1428,104 @@ def search_library(self, query:str=None, entries=True, collations=True,
14161428
# non_entry_count = 0
14171429
# Iterate over all Entries =============================================================
14181430
for entry in self.entries:
1431+
allowed_ext: bool = False if os.path.splitext(entry.filename)[1][1:].lower() in self.ignored_extensions else True
14191432
# try:
14201433
# entry: Entry = self.entries[self.file_to_library_index_map[self._source_filenames[i]]]
14211434
# print(f'{entry}')
14221435

1423-
# If the entry has tags of any kind, append them to this main tag list.
1424-
entry_tags: list[int] = []
1425-
entry_authors: list[str] = []
1426-
if entry.fields:
1427-
for field in entry.fields:
1428-
field_id = list(field.keys())[0]
1429-
if self.get_field_obj(field_id)['type'] == 'tag_box':
1430-
entry_tags.extend(field[field_id])
1431-
if self.get_field_obj(field_id)['name'] == 'Author':
1432-
entry_authors.extend(field[field_id])
1433-
if self.get_field_obj(field_id)['name'] == 'Artist':
1434-
entry_authors.extend(field[field_id])
1435-
1436-
# print(f'Entry Tags: {entry_tags}')
1437-
1438-
# Add Entries from special flags -------------------------------
1439-
# TODO: Come up with a more user-resistent way to 'archived' and 'favorite' tags.
1440-
if only_untagged:
1441-
if not entry_tags:
1442-
results.append((ItemType.ENTRY, entry.id))
1443-
elif only_no_author:
1444-
if not entry_authors:
1445-
results.append((ItemType.ENTRY, entry.id))
1446-
elif only_empty:
1447-
if not entry.fields:
1448-
results.append((ItemType.ENTRY, entry.id))
1449-
elif only_missing:
1450-
if os.path.normpath(f'{self.library_dir}/{entry.path}/{entry.filename}') in self.missing_files:
1451-
results.append((ItemType.ENTRY, entry.id))
1452-
1453-
# elif query == "archived":
1454-
# if entry.tags and self._tag_names_to_tag_id_map[self.archived_word.lower()][0] in entry.tags:
1455-
# self.filtered_file_list.append(file)
1456-
# pb.value = len(self.filtered_file_list)
1457-
# elif query in entry.path.lower():
1436+
if allowed_ext:
1437+
# If the entry has tags of any kind, append them to this main tag list.
1438+
entry_tags: list[int] = []
1439+
entry_authors: list[str] = []
1440+
if entry.fields:
1441+
for field in entry.fields:
1442+
field_id = list(field.keys())[0]
1443+
if self.get_field_obj(field_id)['type'] == 'tag_box':
1444+
entry_tags.extend(field[field_id])
1445+
if self.get_field_obj(field_id)['name'] == 'Author':
1446+
entry_authors.extend(field[field_id])
1447+
if self.get_field_obj(field_id)['name'] == 'Artist':
1448+
entry_authors.extend(field[field_id])
1449+
1450+
# print(f'Entry Tags: {entry_tags}')
1451+
1452+
# Add Entries from special flags -------------------------------
1453+
# TODO: Come up with a more user-resistent way to 'archived' and 'favorite' tags.
1454+
if only_untagged:
1455+
if not entry_tags:
1456+
results.append((ItemType.ENTRY, entry.id))
1457+
elif only_no_author:
1458+
if not entry_authors:
1459+
results.append((ItemType.ENTRY, entry.id))
1460+
elif only_empty:
1461+
if not entry.fields:
1462+
results.append((ItemType.ENTRY, entry.id))
1463+
elif only_missing:
1464+
if os.path.normpath(f'{self.library_dir}/{entry.path}/{entry.filename}') in self.missing_files:
1465+
results.append((ItemType.ENTRY, entry.id))
14581466

1459-
# NOTE: This searches path and filenames.
1460-
if allow_adv:
1461-
if [q for q in query_words if (q in entry.path.lower())]:
1462-
results.append((ItemType.ENTRY, entry.id))
1463-
elif [q for q in query_words if (q in entry.filename.lower())]:
1464-
results.append((ItemType.ENTRY, entry.id))
1465-
elif tag_only:
1466-
if entry.has_tag(self, int(query_words[0])):
1467-
results.append((ItemType.ENTRY, entry.id))
1467+
# elif query == "archived":
1468+
# if entry.tags and self._tag_names_to_tag_id_map[self.archived_word.lower()][0] in entry.tags:
1469+
# self.filtered_file_list.append(file)
1470+
# pb.value = len(self.filtered_file_list)
1471+
# elif query in entry.path.lower():
14681472

1469-
# elif query in entry.filename.lower():
1470-
# self.filtered_entries.append(index)
1471-
elif entry_tags:
1472-
# For each verified, extracted Tag term.
1473-
failure_to_union_terms = False
1474-
for term in all_tag_terms:
1475-
# If the term from the previous loop was already verified:
1476-
if not failure_to_union_terms:
1477-
cluster: set = set()
1478-
# Add the immediate associated Tags to the set (ex. Name, Alias hits)
1479-
# Since this term could technically map to multiple IDs, iterate over it
1480-
# (You're 99.9999999% likely to just get 1 item)
1481-
for id in self._tag_strings_to_id_map[term]:
1482-
cluster.add(id)
1483-
cluster = cluster.union(
1484-
set(self.get_tag_cluster(id)))
1485-
# print(f'Full Cluster: {cluster}')
1486-
# For each of the Tag IDs in the term's ID cluster:
1487-
for t in cluster:
1488-
# Assume that this ID from the cluster is not in the Entry.
1489-
# Wait to see if proven wrong.
1490-
failure_to_union_terms = True
1491-
# If the ID actually is in the Entry,
1492-
if t in entry_tags:
1493-
# There wasn't a failure to find one of the term's cluster IDs in the Entry.
1494-
# There is also no more need to keep checking the rest of the terms in the cluster.
1495-
failure_to_union_terms = False
1496-
# print(f'FOUND MATCH: {t}')
1497-
break
1498-
# print(f'\tFailure to Match: {t}')
1499-
# If there even were tag terms to search through AND they all match an entry
1500-
if all_tag_terms and not failure_to_union_terms:
1501-
# self.filter_entries.append()
1502-
# self.filtered_file_list.append(file)
1503-
# results.append((SearchItemType.ENTRY, entry.id))
1504-
added = False
1505-
for f in entry.fields:
1506-
if self.get_field_attr(f, 'type') == 'collation':
1507-
if (self.get_field_attr(f, 'content') not in collations_added):
1508-
results.append((ItemType.COLLATION, self.get_field_attr(f, 'content')))
1509-
collations_added.append(self.get_field_attr(f, 'content'))
1510-
added = True
1511-
1512-
if not added:
1473+
# NOTE: This searches path and filenames.
1474+
if allow_adv:
1475+
if [q for q in query_words if (q in entry.path.lower())]:
1476+
results.append((ItemType.ENTRY, entry.id))
1477+
elif [q for q in query_words if (q in entry.filename.lower())]:
1478+
results.append((ItemType.ENTRY, entry.id))
1479+
elif tag_only:
1480+
if entry.has_tag(self, int(query_words[0])):
15131481
results.append((ItemType.ENTRY, entry.id))
15141482

1483+
# elif query in entry.filename.lower():
1484+
# self.filtered_entries.append(index)
1485+
elif entry_tags:
1486+
# For each verified, extracted Tag term.
1487+
failure_to_union_terms = False
1488+
for term in all_tag_terms:
1489+
# If the term from the previous loop was already verified:
1490+
if not failure_to_union_terms:
1491+
cluster: set = set()
1492+
# Add the immediate associated Tags to the set (ex. Name, Alias hits)
1493+
# Since this term could technically map to multiple IDs, iterate over it
1494+
# (You're 99.9999999% likely to just get 1 item)
1495+
for id in self._tag_strings_to_id_map[term]:
1496+
cluster.add(id)
1497+
cluster = cluster.union(
1498+
set(self.get_tag_cluster(id)))
1499+
# print(f'Full Cluster: {cluster}')
1500+
# For each of the Tag IDs in the term's ID cluster:
1501+
for t in cluster:
1502+
# Assume that this ID from the cluster is not in the Entry.
1503+
# Wait to see if proven wrong.
1504+
failure_to_union_terms = True
1505+
# If the ID actually is in the Entry,
1506+
if t in entry_tags:
1507+
# There wasn't a failure to find one of the term's cluster IDs in the Entry.
1508+
# There is also no more need to keep checking the rest of the terms in the cluster.
1509+
failure_to_union_terms = False
1510+
# print(f'FOUND MATCH: {t}')
1511+
break
1512+
# print(f'\tFailure to Match: {t}')
1513+
# If there even were tag terms to search through AND they all match an entry
1514+
if all_tag_terms and not failure_to_union_terms:
1515+
# self.filter_entries.append()
1516+
# self.filtered_file_list.append(file)
1517+
# results.append((SearchItemType.ENTRY, entry.id))
1518+
added = False
1519+
for f in entry.fields:
1520+
if self.get_field_attr(f, 'type') == 'collation':
1521+
if (self.get_field_attr(f, 'content') not in collations_added):
1522+
results.append((ItemType.COLLATION, self.get_field_attr(f, 'content')))
1523+
collations_added.append(self.get_field_attr(f, 'content'))
1524+
added = True
1525+
1526+
if not added:
1527+
results.append((ItemType.ENTRY, entry.id))
1528+
15151529
# sys.stdout.write(
15161530
# f'\r[INFO][FILTER]: {len(self.filtered_file_list)} matches found')
15171531
# sys.stdout.flush()
@@ -1536,15 +1550,17 @@ def search_library(self, query:str=None, entries=True, collations=True,
15361550

15371551
for entry in self.entries:
15381552
added = False
1539-
for f in entry.fields:
1540-
if self.get_field_attr(f, 'type') == 'collation':
1541-
if (self.get_field_attr(f, 'content') not in collations_added):
1542-
results.append((ItemType.COLLATION, self.get_field_attr(f, 'content')))
1543-
collations_added.append(self.get_field_attr(f, 'content'))
1544-
added = True
1545-
1546-
if not added:
1547-
results.append((ItemType.ENTRY, entry.id))
1553+
allowed_ext: bool = False if os.path.splitext(entry.filename)[1][1:].lower() in self.ignored_extensions else True
1554+
if allowed_ext:
1555+
for f in entry.fields:
1556+
if self.get_field_attr(f, 'type') == 'collation':
1557+
if (self.get_field_attr(f, 'content') not in collations_added):
1558+
results.append((ItemType.COLLATION, self.get_field_attr(f, 'content')))
1559+
collations_added.append(self.get_field_attr(f, 'content'))
1560+
added = True
1561+
1562+
if not added:
1563+
results.append((ItemType.ENTRY, entry.id))
15481564
# for file in self._source_filenames:
15491565
# self.filtered_file_list.append(file)
15501566
results.reverse()

tagstudio/src/core/ts_core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from src.core.library import Entry, Library
1111

12-
VERSION: str = '9.1.0' # Major.Minor.Patch
12+
VERSION: str = '9.2.0' # Major.Minor.Patch
1313
VERSION_BRANCH: str = 'Alpha' # 'Alpha', 'Beta', or '' for Full Release
1414

1515
# The folder & file names where TagStudio keeps its data relative to a library.

tagstudio/src/qt/ts_qt.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from PySide6.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QPlainTextEdit,
3434
QLineEdit, QScrollArea, QFrame, QTextEdit, QComboBox, QProgressDialog, QFileDialog,
3535
QListView, QSplitter, QSizePolicy, QMessageBox, QBoxLayout, QCheckBox, QSplashScreen,
36-
QMenu)
36+
QMenu, QTableWidget, QTableWidgetItem)
3737
from humanfriendly import format_timespan, format_size
3838

3939
from src.core.library import Collation, Entry, ItemType, Library, Tag
@@ -1969,6 +1969,46 @@ def __init__(self, library:'Library'):
19691969
self.root_layout.addStretch(1)
19701970
self.root_layout.addWidget(self.button_container)
19711971

1972+
class FileExtensionModal(PanelWidget):
1973+
done = Signal()
1974+
def __init__(self, library:'Library'):
1975+
super().__init__()
1976+
self.lib = library
1977+
self.setWindowTitle(f'File Extensions')
1978+
self.setWindowModality(Qt.WindowModality.ApplicationModal)
1979+
self.setMinimumSize(200, 400)
1980+
self.root_layout = QVBoxLayout(self)
1981+
self.root_layout.setContentsMargins(6,6,6,6)
1982+
1983+
self.table = QTableWidget(len(self.lib.ignored_extensions), 1)
1984+
self.table.horizontalHeader().setVisible(False)
1985+
self.table.verticalHeader().setVisible(False)
1986+
self.table.horizontalHeader().setStretchLastSection(True)
1987+
1988+
self.add_button = QPushButton()
1989+
self.add_button.setText('&Add Extension')
1990+
self.add_button.clicked.connect(self.add_item)
1991+
self.add_button.setDefault(True)
1992+
self.add_button.setMinimumWidth(100)
1993+
1994+
self.root_layout.addWidget(self.table)
1995+
self.root_layout.addWidget(self.add_button, alignment=Qt.AlignmentFlag.AlignCenter)
1996+
self.refresh_list()
1997+
1998+
def refresh_list(self):
1999+
for i, ext in enumerate(self.lib.ignored_extensions):
2000+
self.table.setItem(i, 0, QTableWidgetItem(ext))
2001+
2002+
def add_item(self):
2003+
self.table.insertRow(self.table.rowCount())
2004+
2005+
def save(self):
2006+
self.lib.ignored_extensions.clear()
2007+
for i in range(self.table.rowCount()):
2008+
ext = self.table.item(i, 0)
2009+
if ext and ext.text():
2010+
self.lib.ignored_extensions.append(ext.text())
2011+
19722012
class FileOpenerHelper():
19732013
def __init__(self, filepath:str):
19742014
self.filepath = filepath
@@ -3953,6 +3993,10 @@ def start(self):
39533993

39543994
edit_menu.addSeparator()
39553995

3996+
manage_file_extensions_action = QAction('Ignore File Extensions', menu_bar)
3997+
manage_file_extensions_action.triggered.connect(lambda: self.show_file_extension_modal())
3998+
edit_menu.addAction(manage_file_extensions_action)
3999+
39564000
tag_database_action = QAction('Tag Database', menu_bar)
39574001
tag_database_action.triggered.connect(lambda: self.show_tag_database())
39584002
edit_menu.addAction(tag_database_action)
@@ -4128,6 +4172,13 @@ def add_tag_action_callback(self):
41284172
def show_tag_database(self):
41294173
self.modal = PanelModal(TagDatabasePanel(self.lib),'Tag Database', 'Tag Database', has_save=False)
41304174
self.modal.show()
4175+
4176+
def show_file_extension_modal(self):
4177+
# self.modal = FileExtensionModal(self.lib)
4178+
panel = FileExtensionModal(self.lib)
4179+
self.modal = PanelModal(panel, 'Ignored File Extensions', 'Ignored File Extensions', has_save=True)
4180+
self.modal.saved.connect(lambda: (panel.save(), self.filter_items('')))
4181+
self.modal.show()
41314182

41324183
def add_new_files_callback(self):
41334184
"""Runs when user initiates adding new files to the Library."""

0 commit comments

Comments
 (0)