1+ # Makes sure modules are installed
2+ try :
3+ import tkinter as tk
4+ from tkinter import filedialog
5+ from PIL import ImageTk , Image
6+ except ModuleNotFoundError :
7+ print ("Downloading packages..." )
8+ os .system ('pip install tkinter' )
9+ os .system ('pip install pillow' )
10+ print ("Please run the program again." )
11+
12+
13+ import os , json , re
14+ from CustomScroller import ImageScroller
15+
16+ SETTINGS_FILE = os .path .join (os .getcwd (), "webtoonreader_settings.json" )
17+
18+ # To do
19+ # Add bookmarks of where you left off on a manga
20+
21+ class WebtoonReader :
22+ def __init__ (self ):
23+ # Create settings file in the home directory if it does not exist
24+ if not os .path .isfile (SETTINGS_FILE ):
25+ with open (SETTINGS_FILE , "a" ) as f :
26+ json .dump ({"library" : os .getcwd (), "width" : 720 , "height" : 800 , "scroll_speed" : 3 , "recent_chapter" : "" , "recent_chapter_index" : "" }, f )
27+ f .close ()
28+
29+
30+ # Window
31+ self .window = tk .Tk ()
32+ self .window .resizable (False , False )
33+
34+ self .width = get_json ('width' )
35+ self .height = get_json ('height' )
36+ self .scroll_speed = get_json ('scroll_speed' )
37+
38+
39+ # Center Window
40+ x = int (self .window .winfo_screenwidth ()/ 2 - self .width / 2 )
41+ y = int (self .window .winfo_screenheight ()/ 2 - self .height / 2 )
42+ self .window .geometry ("+{}+{}" .format (x , y ))
43+
44+ # ImageScroller
45+ chapter_path = get_json ('recent_chapter' )
46+ self .frame = ImageScroller (self .window , path = chapter_path , scrollbarwidth = 15 , width = self .width , height = self .height , speed = self .scroll_speed )
47+ self .frame .pack ()
48+ manga = os .path .basename (os .path .dirname (chapter_path ))
49+ self .window .title ("[WebtoonReader] - " + manga + ": " + os .path .basename (chapter_path ))
50+
51+ # Menubar
52+ menubar = tk .Menu (self .window )
53+ settingsmenu = tk .Menu (menubar , tearoff = 0 )
54+ settingsmenu .add_command (label = "Load Library" , command = self .get_library )
55+ settingsmenu .add_command (label = "Load Manga" , command = self .load_manga )
56+ settingsmenu .add_command (label = "Load Chapter" , command = lambda : self .create_chapter (None ))
57+ settingsmenu .add_command (label = "Settings" , command = self .set_settings )
58+ settingsmenu .add_command (label = "Help" )
59+
60+ menubar .add_cascade (label = "Settings" , menu = settingsmenu )
61+ menubar .add_cascade (label = "Previous Chapter" , command = self .prev_chapter )
62+ menubar .add_cascade (label = "Next Chapter" , command = self .next_chapter )
63+
64+ # Keybind shortcuts
65+ self .window .bind ("<Left>" , self .key_prev_chapter )
66+ self .window .bind ("<a>" , self .key_prev_chapter )
67+ self .window .bind ("<Right>" , self .key_next_chapter )
68+ self .window .bind ("<d>" , self .key_next_chapter )
69+
70+
71+ # Start the window
72+ self .window .config (menu = menubar )
73+ self .window .mainloop ()
74+
75+
76+ # Loads the directory containing all mangas
77+ def get_library (self ):
78+ library_path = tk .filedialog .askdirectory ()
79+
80+ if library_path != "" :
81+ update_json ('library' , library_path )
82+
83+
84+ # Loads a manga from where the user last left off
85+ def load_manga (self ):
86+ manga_path = tk .filedialog .askdirectory ()
87+
88+ if manga_path == "" :
89+ return
90+
91+ chapter_path = get_json (os .path .basename (manga_path ))
92+ self .create_chapter (chapter_path )
93+
94+
95+ # Loads a chapter to read
96+ def create_chapter (self , path ):
97+ if path != None :
98+ chapter_path = path
99+ else :
100+ chapter_path = tk .filedialog .askdirectory ()
101+ if chapter_path == "" :
102+ return
103+
104+
105+ manga_path = os .path .dirname (chapter_path )
106+ chapter_list = abslistdir (manga_path )
107+ chapter_index = - 1
108+
109+ # Finds the index of the current manga in the directory
110+ for index , chapter in enumerate (chapter_list ):
111+ if chapter == chapter_path :
112+ chapter_index = index
113+ break
114+
115+ # Updates the image scroller
116+ self .frame .destroy ()
117+ self .frame = ImageScroller (self .window , path = chapter_path , scrollbarwidth = 15 , width = self .width , height = self .height , speed = self .scroll_speed )
118+ self .frame .pack ()
119+
120+ # Updates settings json
121+ update_json ('recent_chapter' , chapter_path )
122+ update_json ('recent_chapter_index' , chapter_index )
123+ manga = os .path .basename (manga_path )
124+ update_json (manga , chapter_path )
125+ self .window .title ("[WebtoonReader] - " + manga + ": " + os .path .basename (chapter_path ))
126+
127+
128+ # Finds the next chapter of the manga
129+ def next_chapter (self ):
130+ chapter_index = get_json ('recent_chapter_index' )
131+ chapter_index += 1
132+
133+ # Checks if the current chapter is the last chapter
134+ chapter_path = get_json ('recent_chapter' )
135+ manga_path = os .path .dirname (chapter_path )
136+ chapter_list = abslistdir (manga_path )
137+
138+ # Checks if it is the last chapter
139+ if chapter_index >= len (chapter_list ):
140+ tk .messagebox .showwarning ('Warning' , 'Last Chapter' )
141+ return
142+
143+ chapter_path = chapter_list [chapter_index ]
144+
145+ # Updates the image scroller
146+ self .frame .destroy ()
147+ self .frame = ImageScroller (self .window , path = chapter_path , scrollbarwidth = 15 , width = self .width , height = self .height , speed = self .scroll_speed )
148+ self .frame .pack ()
149+
150+ # Updates the settings json
151+ update_json ('recent_chapter' , chapter_path )
152+ update_json ('recent_chapter_index' , chapter_index )
153+ manga = os .path .basename (manga_path )
154+ update_json (manga , chapter_path )
155+ self .window .title ("[WebtoonReader] - " + manga + ": " + os .path .basename (chapter_path ))
156+
157+
158+ # Finds the previous chapter of the manga
159+ def prev_chapter (self ):
160+ chapter_index = get_json ('recent_chapter_index' )
161+ chapter_index -= 1
162+
163+ # Checks if the current chapter is the first
164+ chapter_path = get_json ('recent_chapter' )
165+ manga_path = os .path .dirname (chapter_path )
166+ chapter_list = abslistdir (manga_path )
167+
168+ # Checks if it is the first chapter
169+ if chapter_index == - 1 :
170+ tk .messagebox .showwarning ('Warning' , 'First Chapter' )
171+ return
172+
173+ chapter_path = chapter_list [chapter_index ]
174+
175+ # Updates the image scroller
176+ self .frame .destroy ()
177+ self .frame = ImageScroller (self .window , path = chapter_path , scrollbarwidth = 15 , width = self .width , height = self .height , speed = self .scroll_speed )
178+ self .frame .pack ()
179+
180+ # Updates the settings json
181+ update_json ('recent_chapter' , chapter_path )
182+ update_json ('recent_chapter_index' , chapter_index )
183+ manga = os .path .basename (manga_path )
184+ update_json (manga , chapter_path )
185+ self .window .title ("[WebtoonReader] - " + manga + ": " + os .path .basename (chapter_path ))
186+
187+
188+ # Keybind shortcut for next chapter
189+ def key_next_chapter (self , e ):
190+ self .next_chapter ()
191+
192+
193+ # Keybind shortcut for previous chapter
194+ def key_prev_chapter (self , e ):
195+ self .prev_chapter ()
196+
197+
198+ # Settings window
199+ def set_settings (self ):
200+ settings = tk .Toplevel ()
201+ x = int (self .window .winfo_screenwidth ()/ 2 - 200 )
202+ y = int (self .window .winfo_screenheight ()/ 2 - 200 )
203+ settings .geometry ("%dx%d+%d+%d" % (400 , 400 , x , y ))
204+
205+ width_label = tk .Label (settings , text = "Width" ).pack (pady = 10 )
206+ self .width_slider = tk .Scale (settings , from_ = 200 , to = self .window .winfo_screenwidth (), orient = tk .HORIZONTAL , length = 300 , resolution = 20 )
207+ self .width_slider .set (self .width )
208+ self .width_slider .pack (pady = 10 )
209+ self .width_slider .bind ("<ButtonRelease-1>" , self .update_width )
210+
211+ height_label = tk .Label (settings , text = "Height" ).pack (pady = 10 )
212+ self .height_slider = tk .Scale (settings , from_ = 200 , to = self .window .winfo_screenheight (), orient = tk .HORIZONTAL , length = 250 , resolution = 20 )
213+ self .height_slider .set (self .height )
214+ self .height_slider .pack (pady = 10 )
215+ self .height_slider .bind ("<ButtonRelease-1>" , self .update_height )
216+
217+ speed_label = tk .Label (settings , text = "Scroll Speed" ).pack (pady = 10 )
218+ self .scroll_speed_slider = tk .Scale (settings , from_ = 1 , to = 5 , orient = tk .HORIZONTAL )
219+ self .scroll_speed_slider .set (self .scroll_speed )
220+ self .scroll_speed_slider .pack (pady = 10 )
221+ self .scroll_speed_slider .bind ("<ButtonRelease-1>" , self .update_speed )
222+
223+ settings .mainloop ()
224+
225+ # Updates width in settings json
226+ def update_width (self , e ):
227+ self .width = self .width_slider .get ()
228+ update_json ('width' , self .width_slider .get ())
229+
230+ # Updates height in settings json
231+ def update_height (self , e ):
232+ self .height = self .height_slider .get ()
233+ update_json ('height' , self .height_slider .get ())
234+
235+ # Updates scroll speed in settings json
236+ def update_speed (self , e ):
237+ self .scroll_speed = self .scroll_speed_slider .get ()
238+ update_json ('scroll_speed' , self .scroll_speed_slider .get ())
239+
240+
241+ # Update the json value if key exists, otherwise adds to json
242+ def update_json (key , value ):
243+ json_file = open (SETTINGS_FILE , "r" )
244+ data = json .load (json_file )
245+ data [key ] = value
246+
247+ json_file = open (SETTINGS_FILE , "w" )
248+ json_file .write (json .dumps (data ))
249+ json_file .close ()
250+
251+
252+ # Get json value (with key)
253+ def get_json (key ):
254+ json_file = open (SETTINGS_FILE , "r" )
255+ data = json .load (json_file )
256+ for item in data :
257+ if key in item :
258+ value = data [key ]
259+ json_file .close ()
260+ return value
261+ json_file .close ()
262+ return None
263+
264+
265+ # Returns a natural sorted list of absolute paths directories
266+ def abslistdir (path ):
267+ list = []
268+ for root , dirs , files in os .walk (path ):
269+ for dir in dirs :
270+ list .append (os .path .join (root , dir ).replace ("\\ " , "/" ))
271+ list .sort (key = natural_sort_key )
272+ return list
273+
274+
275+ # Natural sort a list
276+ def natural_sort_key (list ):
277+ return [int (text ) if text .isdigit () else text .lower ()
278+ for text in re .split (re .compile ('([0-9]+)' ), list )]
279+
280+
281+ # Main
282+ if __name__ == "__main__" :
283+ WebtoonReader ()
0 commit comments