Skip to content

Commit

Permalink
Cover downloader First implementation (MangaManagerORG#201)
Browse files Browse the repository at this point in the history
* Fixed issue opening folder after other folder

* spec improvements.

* Versioning stuff

* First implementation of CoverDownloader

* Bump version hash

* Modified build configurations

* Bump version hash

* Modified build configurations

* Bump version hash

* Bump version hash
  • Loading branch information
ThePromidius authored Jan 13, 2024
1 parent 29248b0 commit dccbbdd
Show file tree
Hide file tree
Showing 17 changed files with 460 additions and 79 deletions.
3 changes: 3 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions .idea/runConfigurations/Build.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/runConfigurations/MangaManager.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions .idea/runConfigurations/MangaManager_DEBUG.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions MangaManager/Extensions/CoverDownloader/CoverData.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import dataclasses
from pathlib import Path
from os.path import splitext
@dataclasses.dataclass
class SeriesCoverData:
normalized_name:str

class CoverData:
# filename:str
# extension:str
#
# volume:str
# locale:str
@property
def dest_filename(self):
"""
The final filename
:return:
"""
return f"Cover{'_Vol.'+str(self.volume).zfill(2) if self.volume else ''}{'_' + self.locale if self.locale else ''}{self.extension}"

def exists(self,dest_path):
return Path(dest_path,self.dest_filename).exists()

def __init__(self,source_filename=None,volume=None,locale=None):

self.source_filename=source_filename
if source_filename:
self.filename, self.extension = splitext(self.source_filename)
else:
self.filename, self.extension = None, None
self.volume=volume
self.locale=locale


def from_cover_data(self,cover_data):
self.source_filename = cover_data.get("attributes").get("fileName")
self.filename, self.extension = splitext(self.source_filename)
self.volume = cover_data.get("attributes").get("volume")
self.locale = cover_data.get("attributes").get("locale")
return self
122 changes: 111 additions & 11 deletions MangaManager/Extensions/CoverDownloader/CoverDownloader.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,127 @@
from tkinter import Label, Frame, Entry

# import tkinter
# from idlelib.tooltip import Hovertip
# from idlelib.tooltip import Hovertip
import logging
import urllib.request
import os
import platform
import subprocess
from enum import StrEnum
from pathlib import Path
from tkinter import Frame, Label, StringVar
from tkinter.ttk import Entry, Button, Frame
import slugify
import requests

from Extensions.CoverDownloader.CoverData import CoverData
from Extensions.CoverDownloader.MangaDex import parse_mangadex_id
from Extensions.CoverDownloader.exceptions import UrlNotFound
from Extensions.IExtensionApp import IExtensionApp
# from src.MetadataManager.GUI.tooltip import ToolTip
from src.Settings import Settings, SettingHeading, SettingSection, SettingControl, SettingControlType

logger = logging.getLogger()
def get_cover_from_source_dummy() -> list:
...


class CoverDownloader():#IExtensionApp):
class CoverDownloaderSetting(StrEnum):
ForceOverwrite = "force_overwrite" # If existing covers should be re-downloaded and overwritten
class CoverDownloader(IExtensionApp):
name = "Cover Downloader"
embedded_ui = True

def init_settings(self):

self.settings = [
SettingSection(self.name,self.name,[
SettingControl(
key=CoverDownloaderSetting.ForceOverwrite,
name="Force Overwrite",
control_type=SettingControlType.Bool,
value=False,
tooltip="Enable if existing covers should be re-downloaded and overwritten"
)
])
]
super().init_settings()

@property
def output_folder(self):
return Settings().get(SettingHeading.Main, 'covers_folder_path')

def serve_gui(self):
if not self.master:
return Exception("Tried to initialize ui with no master window")
self.url_var = StringVar(name="mdex_cover_url")
self.dest_path_var = StringVar(name="dest_path",value=str(Path((self.output_folder or Path(Path.home(),"Pictures","Manga Covers")),
'<Manga Name Folder>')))
Label(self.master,text="MangaManager will download all availabe images from").pack()
# self.frame_3 = Frame(frame1)
Label(self.master, text='MANGADEX MANGA ID / URL').pack(side='top')
Entry(master=self.master,textvariable=self.url_var).pack(side='top')

Label(self.master, text="to the folder:").pack()
label_path = Label(self.master,textvariable=self.dest_path_var)
label_path.pack(pady="10 30", side='top')

self.button_1 = Button(self.master, text='Correct! Start!', command=self.process_download)
self.button_1.pack(side='top')
self.button_explorer = Button(self.master, text='Open explorer', command=self.open_explorer)

def process_download(self):
try:
self.download()
except Exception as e:
logger.exception("Error downloading")
finally:
self.show_open_explorer_btn()

def show_open_explorer_btn(self):
self.button_explorer.pack(side='top')

def open_explorer(self):

frame = Frame(self.master)
frame.pack()
path = self.dest_path_var.get()
if platform.system() == "Windows":
os.startfile(path)
elif platform.system() == "Darwin":
subprocess.Popen(["open", path])
else:
subprocess.Popen(["xdg-open", path])
...

Label(frame, text="Manga identifier").pack()
Entry(frame).pack()
# Combobox(frame, state="readonly",values=sources_factory["CoverSources"]).pack()
def download(self):
...
mdex_id = parse_mangadex_id(self.url_var.get())
logger.debug(f"Parsed mdex id: '{mdex_id}'")
data = {"manga[]": [mdex_id], "includes[]": ["manga"], "limit": 50}
# Request the list of covers in the prrovided manga
r = requests.get(f"https://api.mangadex.org/cover", params=data)

covers = get_cover_from_source_dummy()
if r.status_code == 400:

raise UrlNotFound(r.url)
data = r.json().get("data")
cover_attributes = data[0].get("relationships")[0].get("attributes")

ja_title = list(filter(lambda p: p.get("ja-ro"),
cover_attributes.get("altTitles")))
if ja_title:
ja_title = ja_title[0].get("ja-ro")
full_name = (ja_title or cover_attributes.get("title").get("en"))
normalized_manga_name = slugify.slugify(full_name[:50])
destination_dirpath = Path((self.output_folder or Path(Path.home(), "Pictures", "Manga Covers")),normalized_manga_name)
self.dest_path_var.set(str(destination_dirpath))
self.update()
destination_dirpath.mkdir(parents=True, exist_ok=True)
_total = len(data)

for i,cover_data in enumerate(data):
cover = CoverData().from_cover_data(cover_data)
image_path = Path(destination_dirpath, cover.dest_filename)
if not cover.exists(image_path) or Settings().get(self.name,CoverDownloaderSetting.ForceOverwrite):
image_url = f"https://mangadex.org/covers/{mdex_id}/{cover.source_filename}"

urllib.request.urlretrieve(image_url, image_path)
logger.debug(f"Downloaded {image_path}")
else:
logger.info(f"Skipped https://mangadex.org/covers/{mdex_id}/{cover.source_filename} -> Already exists")
83 changes: 83 additions & 0 deletions MangaManager/Extensions/CoverDownloader/MangaDex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import requests

from Extensions.CoverDownloader.exceptions import UrlNotFound


def parse_mangadex_id(value) -> str:
"""
Accepts a mangadex id or URL
:param value:
:return: mangadex managa id
"""

if "https://mangadex.org/title/" in value:
value = value.replace("https://mangadex.org/title/", "").split("/")[0]
return value


def download_covers(self, manga_id, forceOverwrite=False, test_run=False):
"""
Downloads the covers from manga_id from mangadex.
If the cover is already downloaded it won't re-download
:param manga_id: The manga id only.
:param cover_folder: The folder where the series folder will be created.
:param forceOverwrite: If existing covers should be re-downloaded and overwritten
:param test_run: Value to make a dry run where it asserts that each cover url is valid
:return:
"""

data = {"manga[]": [manga_id], "includes[]": ["manga"], "limit": 50}
# Request the list of covers in the prrovided manga
r = requests.get(f"https://api.mangadex.org/cover", params=data)

if r.status_code == 400:
raise UrlNotFound(r.url)

r_json = r.json()
cover_attributes = r_json.get("data")[0].get("relationships")[0].get("attributes")

ja_title = list(filter(lambda p: p.get("ja-ro"),
cover_attributes.get("altTitles")))
if ja_title:
ja_title = ja_title[0].get("ja-ro")

normalized_manga_name = (ja_title or cover_attributes.get("title").get("en"))




destination_dirpath = pathlib.Path(self.settings.get('cover_folder_path'), cleanFilename(
normalized_manga_name)) # The covers get stored in their own series folder inside the covers directory
if not test_run:
destination_dirpath.mkdir(parents=True, exist_ok=True)
total = len(r_json.get("data"))
self.progressbar = ProgressBar(self._initialized_UI, self._progressbar_frame, total)

for i, cover_data in enumerate(r_json.get("data")):
try:

cover_filename = cover_data.get("attributes").get("fileName")
filename, file_extension = os.path.splitext(cover_filename)

cover_volume = cover_data.get("attributes").get("volume")
cover_loc = cover_data.get("attributes").get("locale")

destination_filename = f"Cover_Vol.{str(cover_volume).zfill(2)}_{cover_loc}{file_extension}"
destination_filepath = pathlib.Path(destination_dirpath, destination_filename)
if (not destination_filepath.exists() or forceOverwrite) and not test_run:
image_url = f"https://mangadex.org/covers/{manga_id}/{cover_filename}"
urllib.request.urlretrieve(image_url, destination_filepath)
logger.debug(f"Downloaded {destination_filename}")
elif test_run:
image_url = f"https://mangadex.org/covers/{manga_id}/{cover_filename}"
print(f"Asserting if valid url: '{image_url}' ")
return check_url_isImage(image_url)
else:
logger.info(f"Skipped 'https://mangadex.org/covers/{manga_id}/{cover_filename}' -> Already exists")
self.progressbar.increaseCount()
except Exception as e:
logger.error(e)
self.progressbar.increaseError()
logger.info(f"Files saved to: '{destination_dirpath}'")
print(f"Files saved to: '{destination_dirpath}'")
23 changes: 23 additions & 0 deletions MangaManager/Extensions/CoverDownloader/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class CoverDoesNotExist(Exception):
pass


class NoOverwriteSelected(Exception):
pass


class NoCoverFile(FileNotFoundError):
"""
Exception raised when cover path is not specified or not found.
"""

def __init__(self, coverFilePath, parent_window=None):
super().__init__(f'Cover image file path not provided or image not found: {coverFilePath}')

class UrlNotFound(Exception):
"""
Exception raised when api returns 400 status code
"""

def __init__(self, requestUrl):
super().__init__(f'Url {requestUrl} not found')
Loading

0 comments on commit dccbbdd

Please sign in to comment.