Skip to content

Commit 3fecc6d

Browse files
authored
v2.5.25
1 parent 80e3c87 commit 3fecc6d

File tree

1 file changed

+83
-2
lines changed

1 file changed

+83
-2
lines changed

Diff for: komga_cover_extractor.py

+83-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
import settings as settings_file
4747

4848
# Version of the script
49-
script_version = (2, 5, 24)
49+
script_version = (2, 5, 25)
5050
script_version_text = "v{}.{}.{}".format(*script_version)
5151

5252
# Paths = existing library
@@ -238,7 +238,7 @@ def __str__(self):
238238
"light novel", # name
239239
novel_extensions, # extensions
240240
[
241-
r"\[[^\]]*(Lucaz|Stick|Oak|Yen (Press|On)|J-Novel|Seven Seas|Vertical|One Peace Books|Cross Infinite|Sol Press|Hanashi Media|Kodansha|Tentai Books|SB Creative|Hobby Japan|Impress Corporation|KADOKAWA)[^\]]*\]|(faratnis)"
241+
r"\[[^\]]*(Lucaz|Stick|Oak|Yen (Press|On)|J-Novel|Seven Seas|Vertical|One Peace Books|Cross Infinite|Sol Press|Hanashi Media|Kodansha|Tentai Books|SB Creative|Hobby Japan|Impress Corporation|KADOKAWA|Viz Media)[^\]]*\]|(faratnis)"
242242
], # must_contain
243243
[], # must_not_contain
244244
),
@@ -2578,6 +2578,10 @@ def get_series_name_from_volume(name, root, test_mode=False, second=False):
25782578
# replace _extra
25792579
name = remove_dual_space(name.replace("_extra", ".5")).strip()
25802580

2581+
# Replace "- One-shot" after series name
2582+
if "one" in name.lower() and "shot" in name.lower():
2583+
name = re.sub(r"(-\s*)One(-|)shot\s*", "", name, flags=re.IGNORECASE).strip()
2584+
25812585
# replace underscores
25822586
name = replace_underscores(name) if "_" in name else name
25832587

@@ -2747,6 +2751,10 @@ def get_series_name_from_chapter(name, root, chapter_number="", second=False):
27472751
# Replace _extra
27482752
name = name.replace("_extra", ".5")
27492753

2754+
# Replace "- One-shot" after series name
2755+
if "one" in name.lower() and "shot" in name.lower():
2756+
name = re.sub(r"(-\s*)One(-|)shot\s*", "", name, flags=re.IGNORECASE).strip()
2757+
27502758
# Remove dual space
27512759
name = remove_dual_space(name).strip()
27522760

@@ -3268,6 +3276,69 @@ def clean_publisher_name(name):
32683276
return publisher
32693277

32703278

3279+
# Function to determine if an image is black and white with better handling for halftones
3280+
def is_image_black_and_white(image, tolerance=15, threshold=200):
3281+
"""
3282+
Determines if an image is black and white by verifying that
3283+
most pixels are grayscale (R == G == B) and fall within the black or white range.
3284+
3285+
Args:
3286+
image (PIL.Image): The image to check.
3287+
tolerance (int): The allowed difference between R, G, and B for a pixel to be considered grayscale.
3288+
threshold (int): The number of pixels that need to be grayscale or black/white to count as a valid black-and-white image.
3289+
3290+
Returns:
3291+
bool: True if the image is black and white or grayscale, False otherwise.
3292+
"""
3293+
try:
3294+
# Convert the image to RGB (ensures consistent handling of image modes)
3295+
image_rgb = image.convert("RGB")
3296+
3297+
# Extract pixel data
3298+
pixels = list(image_rgb.getdata())
3299+
3300+
# Count pixels that are grayscale and black/white
3301+
grayscale_count = 0
3302+
3303+
for r, g, b in pixels:
3304+
# Check if the pixel is grayscale within the tolerance
3305+
if abs(r - g) <= tolerance and abs(g - b) <= tolerance:
3306+
# Further check if it is black or white
3307+
if r == 0 or r == 255:
3308+
grayscale_count += 1
3309+
elif 0 < r < 255:
3310+
grayscale_count += 1
3311+
3312+
# If enough pixels are grayscale or black/white, return True
3313+
if grayscale_count / len(pixels) > 0.9:
3314+
return True
3315+
3316+
return False # Otherwise, it's not black and white
3317+
except Exception as e:
3318+
send_message(f"Error checking if image is black and white: {e}", error=True)
3319+
return False
3320+
3321+
3322+
# Function to check if the first image in a zip file is black and white
3323+
def is_first_image_black_and_white(zip_path):
3324+
try:
3325+
with zipfile.ZipFile(zip_path, "r") as zip_file:
3326+
# Sort files alphabetically and get the first one
3327+
sorted_files = sorted(zip_file.namelist())
3328+
if not sorted_files:
3329+
return False # No files in the archive
3330+
3331+
first_file = sorted_files[0]
3332+
if get_file_extension(first_file) in image_extensions:
3333+
with zip_file.open(first_file) as image_file:
3334+
image = Image.open(io.BytesIO(image_file.read()))
3335+
return is_image_black_and_white(image)
3336+
return False
3337+
except Exception as e:
3338+
send_message(f"Error processing zip file {zip_path}: {e}", error=True)
3339+
return False
3340+
3341+
32713342
# Trades out our regular files for file objects
32723343
def upgrade_to_volume_class(
32733344
files,
@@ -3381,6 +3452,16 @@ def upgrade_to_volume_class(
33813452
subtitle=file_obj.subtitle,
33823453
)
33833454

3455+
if (
3456+
not test_mode
3457+
and file_obj.file_type != "chapter"
3458+
and not file_obj.volume_number
3459+
and check_for_exception_keywords(file_obj.name, exception_keywords)
3460+
and is_first_image_black_and_white(file_obj.path)
3461+
):
3462+
file_obj.file_type = "chapter"
3463+
file_obj.is_one_shot = True
3464+
33843465
if file_obj.is_one_shot:
33853466
file_obj.volume_number = 1
33863467

0 commit comments

Comments
 (0)