Skip to content

Commit 0646508

Browse files
authored
Fix Raw Image Handling and Improve Text File Encoding Compatibility (#233)
* Fix text and RAW image handling - Fix RAW images not being loaded correctly in the preview panel - Fix trying to read size data from null images - Refactor `os.stat` to `<Path object>.stat()` - Remove unnecessary upper/lower conversions - Improve encoding compatibility beyond UTF-8 when reading text files - Code cleanup * Use chardet for character encoding detection
1 parent 0137ed5 commit 0646508

File tree

5 files changed

+50
-31
lines changed

5 files changed

+50
-31
lines changed

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ typing_extensions>=3.10.0.0,<=4.11.0
88
ujson>=5.8.0,<=5.9.0
99
rawpy==0.21.0
1010
pillow-heif==0.16.0
11+
chardet==5.2.0

tagstudio/src/core/library.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -856,10 +856,7 @@ def refresh_dir(self) -> Generator:
856856
# for type in TYPES:
857857
start_time = time.time()
858858
for f in self.library_dir.glob("**/*"):
859-
# p = Path(os.path.normpath(f))
860859
try:
861-
if f.is_dir():
862-
print(f)
863860
if (
864861
"$RECYCLE.BIN" not in f.parts
865862
and TS_FOLDER_NAME not in f.parts
@@ -878,14 +875,11 @@ def refresh_dir(self) -> Generator:
878875
logging.info(
879876
f"The File/Folder {f} cannot be accessed, because it requires higher permission!"
880877
)
881-
# sys.stdout.write(f'\r[LIBRARY] {self.dir_file_count} files found in "{self.library_dir}"...')
882-
# sys.stdout.flush()
883878
end_time = time.time()
884879
# Yield output every 1/30 of a second
885880
if (end_time - start_time) > 0.034:
886881
yield self.dir_file_count
887882
start_time = time.time()
888-
# print('')
889883
# Sorts the files by date modified, descending.
890884
if len(self.files_not_in_library) <= 100000:
891885
try:
@@ -895,12 +889,12 @@ def refresh_dir(self) -> Generator:
895889
)
896890
except (FileExistsError, FileNotFoundError):
897891
print(
898-
f"[LIBRARY] [ERROR] Couldn't sort files, some were moved during the scanning/sorting process."
892+
"[LIBRARY] [ERROR] Couldn't sort files, some were moved during the scanning/sorting process."
899893
)
900894
pass
901895
else:
902896
print(
903-
f"[LIBRARY][INFO] Not bothering to sort files because there's OVER 100,000! Better sorting methods will be added in the future."
897+
"[LIBRARY][INFO] Not bothering to sort files because there's OVER 100,000! Better sorting methods will be added in the future."
904898
)
905899

906900
def refresh_missing_files(self):

tagstudio/src/core/utils/encoding.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
2+
# Licensed under the GPL-3.0 License.
3+
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
4+
5+
from chardet.universaldetector import UniversalDetector
6+
from pathlib import Path
7+
8+
9+
def detect_char_encoding(filepath: Path) -> str | None:
10+
"""
11+
Attempts to detect the character encoding of a text file.
12+
13+
Args:
14+
filepath (Path): The path of the text file to analyze.
15+
16+
Returns:
17+
str | None: The detected character encoding, if any.
18+
"""
19+
20+
detector = UniversalDetector()
21+
with open(filepath, "rb") as text_file:
22+
for line in text_file.readlines():
23+
detector.feed(line)
24+
if detector.done:
25+
break
26+
detector.close()
27+
return detector.result["encoding"]

tagstudio/src/qt/widgets/preview_panel.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
44

55
import logging
6-
import os
76
from pathlib import Path
87
import time
98
import typing
@@ -41,17 +40,16 @@
4140
from src.qt.widgets.panel import PanelModal
4241
from src.qt.widgets.text_box_edit import EditTextBox
4342
from src.qt.widgets.text_line_edit import EditTextLine
44-
from src.qt.widgets.item_thumb import ItemThumb
4543
from src.qt.widgets.video_player import VideoPlayer
4644

4745

4846
# Only import for type checking/autocompletion, will not be imported at runtime.
4947
if typing.TYPE_CHECKING:
5048
from src.qt.ts_qt import QtDriver
5149

52-
ERROR = f"[ERROR]"
53-
WARNING = f"[WARNING]"
54-
INFO = f"[INFO]"
50+
ERROR = "[ERROR]"
51+
WARNING = "[WARNING]"
52+
INFO = "[INFO]"
5553

5654
logging.basicConfig(format="%(message)s", level=logging.INFO)
5755

@@ -443,7 +441,7 @@ def update_widgets(self):
443441
# 0 Selected Items
444442
if not self.driver.selected:
445443
if self.selected or not self.initialized:
446-
self.file_label.setText(f"No Items Selected")
444+
self.file_label.setText("No Items Selected")
447445
self.file_label.setFilePath("")
448446
self.file_label.setCursor(Qt.CursorShape.ArrowCursor)
449447

@@ -516,7 +514,7 @@ def update_widgets(self):
516514
image = Image.open(str(filepath))
517515
elif filepath.suffix.lower() in RAW_IMAGE_TYPES:
518516
try:
519-
with rawpy.imread(filepath) as raw:
517+
with rawpy.imread(str(filepath)) as raw:
520518
rgb = raw.postprocess()
521519
image = Image.new(
522520
"L", (rgb.shape[1], rgb.shape[0]), color="black"
@@ -546,30 +544,28 @@ def update_widgets(self):
546544
self.preview_vid.show()
547545

548546
# Stats for specific file types are displayed here.
549-
if filepath.suffix.lower() in (
547+
if image and filepath.suffix.lower() in (
550548
IMAGE_TYPES + VIDEO_TYPES + RAW_IMAGE_TYPES
551549
):
552550
self.dimensions_label.setText(
553-
f"{filepath.suffix.lower().upper()[1:]}{format_size(os.stat(filepath).st_size)}\n{image.width} x {image.height} px"
551+
f"{filepath.suffix.upper()[1:]}{format_size(filepath.stat().st_size)}\n{image.width} x {image.height} px"
554552
)
555553
else:
556554
self.dimensions_label.setText(
557-
f"{filepath.suffix.lower().upper()[1:]}{format_size(os.stat(filepath).st_size)}"
555+
f"{filepath.suffix.upper()[1:]}{format_size(filepath.stat().st_size)}"
558556
)
559557

560558
if not filepath.is_file():
561559
raise FileNotFoundError
562560

563561
except FileNotFoundError as e:
564-
self.dimensions_label.setText(
565-
f"{filepath.suffix.lower().upper()[1:]}"
566-
)
562+
self.dimensions_label.setText(f"{filepath.suffix.upper()[1:]}")
567563
logging.info(
568564
f"[PreviewPanel][ERROR] Couldn't Render thumbnail for {filepath} (because of {e})"
569565
)
570566

571567
except (FileNotFoundError, cv2.error) as e:
572-
self.dimensions_label.setText(f"{extension.upper()}")
568+
self.dimensions_label.setText(f"{filepath.suffix.upper()}")
573569
logging.info(
574570
f"[PreviewPanel][ERROR] Couldn't Render thumbnail for {filepath} (because of {e})"
575571
)
@@ -578,7 +574,7 @@ def update_widgets(self):
578574
DecompressionBombError,
579575
) as e:
580576
self.dimensions_label.setText(
581-
f"{filepath.suffix.lower().upper()[1:]}{format_size(os.stat(filepath).st_size)}"
577+
f"{filepath.suffix.upper()[1:]}{format_size(filepath.stat().st_size)}"
582578
)
583579
logging.info(
584580
f"[PreviewPanel][ERROR] Couldn't Render thumbnail for {filepath} (because of {e})"
@@ -773,7 +769,7 @@ def set_tags_updated_slot(self, slot: object):
773769
self.tags_updated.disconnect()
774770
except RuntimeError:
775771
pass
776-
logging.info(f"[UPDATE CONTAINER] Setting tags updated slot")
772+
logging.info("[UPDATE CONTAINER] Setting tags updated slot")
777773
self.tags_updated.connect(slot)
778774

779775
# def write_container(self, item:Union[Entry, Collation, Tag], index, field):

tagstudio/src/qt/widgets/thumb_renderer.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import logging
77
import math
8-
import os
98
from pathlib import Path
109

1110
import cv2
@@ -30,6 +29,7 @@
3029
IMAGE_TYPES,
3130
RAW_IMAGE_TYPES,
3231
)
32+
from src.core.utils.encoding import detect_char_encoding
3333

3434
ImageFile.LOAD_TRUNCATED_IMAGES = True
3535

@@ -134,7 +134,7 @@ def render(
134134
image = ImageOps.exif_transpose(image)
135135
except DecompressionBombError as e:
136136
logging.info(
137-
f"[ThumbRenderer]{WARNING} Couldn't Render thumbnail for {_filepath} (because of {e})"
137+
f"[ThumbRenderer]{WARNING} Couldn't Render thumbnail for {_filepath.name} ({type(e).__name__})"
138138
)
139139

140140
elif _filepath.suffix.lower() in RAW_IMAGE_TYPES:
@@ -149,14 +149,14 @@ def render(
149149
)
150150
except DecompressionBombError as e:
151151
logging.info(
152-
f"[ThumbRenderer]{WARNING} Couldn't Render thumbnail for {_filepath} (because of {e})"
152+
f"[ThumbRenderer]{WARNING} Couldn't Render thumbnail for {_filepath.name} ({type(e).__name__})"
153153
)
154154
except (
155155
rawpy._rawpy.LibRawIOError,
156156
rawpy._rawpy.LibRawFileUnsupportedError,
157-
):
157+
) as e:
158158
logging.info(
159-
f"[ThumbRenderer]{ERROR} Couldn't Render thumbnail for raw image {_filepath}"
159+
f"[ThumbRenderer]{ERROR} Couldn't Render thumbnail for raw image {_filepath.name} ({type(e).__name__})"
160160
)
161161

162162
# Videos =======================================================
@@ -178,7 +178,8 @@ def render(
178178

179179
# Plain Text ===================================================
180180
elif _filepath.suffix.lower() in PLAINTEXT_TYPES:
181-
with open(_filepath, "r", encoding="utf-8") as text_file:
181+
encoding = detect_char_encoding(_filepath)
182+
with open(_filepath, "r", encoding=encoding) as text_file:
182183
text = text_file.read(256)
183184
bg = Image.new("RGB", (256, 256), color="#1e1e1e")
184185
draw = ImageDraw.Draw(bg)
@@ -268,7 +269,7 @@ def render(
268269
) as e:
269270
if e is not UnicodeDecodeError:
270271
logging.info(
271-
f"[ThumbRenderer]{ERROR}: Couldn't render thumbnail for {_filepath} ({e})"
272+
f"[ThumbRenderer]{ERROR}: Couldn't render thumbnail for {_filepath.name} ({type(e).__name__})"
272273
)
273274
if update_on_ratio_change:
274275
self.updated_ratio.emit(1)

0 commit comments

Comments
 (0)