a$h
zZccqZbE-K9dWWUG9a`OA^~9BYJq&yqI1bD~-5Sul;jTT;_`A(rR_L#kYK@G3uia~g
z9anY7l{EE5@L*7AuC0L;`$&Fwp!rPNd#P?-eLi!l-xoOm%+fk6eTQft?SBCp6LlPz
zgE}>!_oCkhhl5#Kho!jEJG*CspMp9$^|{QaG;jrY4A2-^b}!i0`@o*D3-!aTVLuLC!NNpS0P&MvQ4qABhDs=XiD{AiW=CLbf*p8N~J{{qK=IjBWnG96MeN}N|=>@!CzAK(T4!N8~50SW2>&%x&S?G
z1!g6rGC$>Sg!_`$TrRK0a$MCBS3gEe*?ilX3M!n9VoLd4fcB;GS}e!axN&tUI=Ua2
zwU8?7ueO1QlfkdRxV*aj>~i>3^rJnAW-_LN)AB!u<}~qZ?
z13^cmn}bJzZ-H5yccC@o*<3c#*%N|2u9X34D1z2voeOkNG_CuuCR|5jHUAFOXSezu
zuvYQ-WR(mA9p&kLNKXfU22IfWwCZpT{;IEbOjRnyhEW*^dLq@BkLF$645p>&CS2zn
z{M{WCefOvw`gk+Y%Rrtdb^+*ggQAfKR&fuJo?&0EoYwO@ltYPt#M{u(~+0~}XnqVQ*9
zGZ3^zx+ORPw0+Hp?sMM&Ja=?#_4ov344|!W52#f8T^tEC|DXwd%B1R``C3PV!uK+6
z{Fxb8E(5W)^89|J9|szPo|LXv`n(^-w_Snn0hcQrms^s7pszgDJJNT6wyqi3fN#$S
zdavNPT2dW8U>pX5#z?mUF9(|6v(o*_B(DEG{5lXgrp6JB%MQgr&>5-b9y}3T1SX;D
zl|JWt@JrtvL-FE+%)mGdNM|{;|Hs3?JZRgRksI*oSfKHlap-bcGcXhb(piMP$bSH|
z?HTKv_~h}5p%meR>R=#>DaF@L;FaK7u+sN!bzT1t_;M=9=GfN3na^PcmSO7u
z53dDndOy5?KSu;Vm*T^AGf)=;L35;f{x}rOgSM<6);?}pW0rk?)Ww<4V+NLDAZRX6
z?@xLbXu~s>e0Xl|*HSdtZU$;)Am}bncP7<)tkm~q)$`fK;DI3PtCaem8JHFWvF^z0
z9nU|41(5o9`)T;FE$}_yw8Z4=bTJULM|wN(*Wjxl<+##*9Y+JlR98#;k7+Rwv=^mX
zqyA~2wY7DPcTe*?`abvyxI35@jlPZ<=wm?oi*Rr9ZwJ2z(~O6@uBCNW{|fvYGJP%W
z52nokIt;OOXYwb4>p@*wpXPbA&gu~0d%$Un$k*#*An1@(?}s%9_cD;;GnV!S)bC|D
zuKHTpA55EpphHrvQ$Gw`2vUnH?F;if5c=L})9CA&fr5cphk5=1q-TTF#xDMcIvAOW`?lW%CLJToIsfOV
z*FAvm0asQwzWNdj1dZnDcBC%?SApf|ev)PWNS$5?w8q(SwM4txyz&eLosw<=)_@;^
zNyf->&b>gL4g`*=m6wcbFa!fZtE6Ggh~@|_NBfg1qxCC#XPxZ}GlWY%#0;!B1L!sD
z1M*a37as?@@0wJcjCx+Zt9~lb*vg9Yg{H|@!
zDOlE!Q_PbDjF-*TQiDT4P>YbJQs#FE#dWU$5Q>_`%?~JU}
zni1^{xR>L~;!K@^py52#*oEc}t;AlP8n4iPhTaz@by2zgabKnUJ;B?-Z^0z)^9G$$
zzh&TcR@P)|7(mY%9S6qbfTR#g*O}JqcV62K6m<+caLGcUGEP
zpT)Ra#SEm(K+tiXO4mn%X|EZ%glqbpRmwtg-TN5GV=CMW>bE*R38uN<#k;tke}B8L
zW&AAWgH1u|G`jv~po;-%I>+{;F9G`Pj=G1V8SxyCNxx^3XnKA3@C27c
zMEk<%9klm_>1_!A-6{sqcy>QV8uq)mhJ4xmTOFT$pL6^7tXicYJJZWR6jO>V?bWGy
z7e4@X#8BBe-pRSO=F87ky^Z0&Tg5=oIH|@iwBN;7!6ep<+{k&42BCj!6_0kt48#mX
zI@dFg_U=0yYyf5b%{ZTFp42^n`>MDBoNpxqLFc53tDS+yF6y4!uK7`?0I#!ZrHbrS
z83X7&+?#Dh{>5McjMF#umeo6}gMs_1vIg*Htz;nRom68N#{!Mp^~T0>|GtfLX-@A>
zpq2dDDKk*Ufb^b2V;6^l^Fdv6+qDk)xgcDltP1|j4784cNb`Ee+6%lN`~fVtkBZA(
z#CiPAs&%TdLrXD$?u)*J{Ep!D;06%K$8z&;=R9`=ezsbwA#86I13~k7s(BaB0(I;u
z_)E_7xct1W;>^yNfngX3+9%bz{G)*G-vU{j=KZ*Rl_ubk7*qPLHl`n0IBv$
z8fWZ6`w?o)-oIznI-T@Q0lo)pn^5e}&enFb*+ndNF18$R)p2YGrw()WQ;=eBEKn74uKoz+Mc@WE!Fr3?@c
zVf;et@?Qx?eLt)@wi>7Rb5%<KW?@@HH^(GnVcdPXwA5@3?BAM(on)41`#S(*4MP2n_p-^%IWOerb-W(dl=I7BdiH
zB2RZAeJ!{i3^NvZI>%{kWQ!THYi3}13@9#gXn*NvfMM1-{+i?VRXP7N17@I~0mViH
z?a_2T7}Q_sKJhrPY5&>$i5V~hVu1LF5!N|Y?JuqORi}W>&7K)B1Jw-l#Fh4?cn#26
zs@^_J^W9Gdj;U%oZ7~BG1H?((Uum7kTJT*E$JKTG{SdH;X)^<6V0Z?I8O4|Oyx1Rn
z6zCnX_Se)qD$i3Io=P8S1~LYSo4C)?o)<@g`vApRW;0c0zzmGeK!~5-bo4X1gc&dc
zX21-X0W)9*%zzm%17^Ssm;p0j2F!pNFau`54445kuo4Vxto*lOuF|w|Wo#R^EUQtc
zE;ns5z*g!>r`*Co+v<{&hH~2uu$5Ibl-sPb+)Z=UwheQ0>-r0J&WUYxmF?EKxm5#f
zB~EnCNx4efZF6N6?Uch-!f3~~5!<@H%H+27gKhmzsn3aEL-l=51e?h2b)w7G!*;NV>|V)q*?OD^wv6l^C%SAs
zEm7Fk>w?{EciXzzE^PBE*X=}MTac~0B??2jji7CW(Mo6pL{PAu9MGF#jdy|%a|dTlY=
zi?*zhx^%y2>oi2{M4v5kV$s$iJN^b*v~|diTVm1HAvTOSTD(vF;DD?VDxWQ11O*2##ipmDKSCnhRvs|S?J0=uGICZ`Hp#`Y4nGXJuc
zpj-(roqj^L`N*-5UDfe@<3ji#O67#g74(x*5h@2|t8yZpJHwZHd*nZ6T^e$gaw5
zWo&Po+sd8j)U(Wq&GK?(Em7FkhZ-!Vg>8QEvyj^)oU?@O!dBu$VJmT>u$4Gb*h<)5
zwDniC@S#Nai?)6zwhaD}=Ku1}VSz8^4%di%;U+Fn&+TgaU#yYC6MMbZi%if^DYTd3$F
zp^N1L+oiVkoywHj<~z2audV6N7B4O3QVe2!wXkCD(7ct>Z)>w7Xo#i9l$#t>Z)>wA_net6C%e~{eN|T3GAGt$&i9qeDq3O7D!RJ7+#p+4(N*Q;vWiyORM8IEef7)&
zv_o3IZGm!CHaXZKw7;AxT4j?gI)wI@%Oq3bUslm|GM1EE;G|VHRkX?`ZLj8(W#^O=
zRW?;LBebM0YKcnQd`@UtxrG5XWRVeCRt_gJLd$G8QE5{}D}2u$L@I4Ku?}O|_4HL)
zX``a6tIAQ)RRe4ywDfwkM73>xfbFIMwhaSpw+^t~Ho&%VkZqybhmC`58!9=2Fa0a$
ItckY&2Rh#)L;wH)
literal 0
HcmV?d00001
diff --git a/temp/this directory is necessary b/temp/this directory is necessary
new file mode 100644
index 0000000..51501d7
--- /dev/null
+++ b/temp/this directory is necessary
@@ -0,0 +1 @@
+temp directory :D
\ No newline at end of file
diff --git a/widgets/Video.py b/widgets/Video.py
new file mode 100644
index 0000000..807e27b
--- /dev/null
+++ b/widgets/Video.py
@@ -0,0 +1,193 @@
+import customtkinter as ctk
+import tkinter as tk
+import webbrowser
+import time
+import threading
+from functions.getColor import getColor
+from functions.convertTime import convertTime
+
+class Video(ctk.CTkFrame):
+ def __init__(self,
+ master,
+ border_width=None,
+ width=None,
+ height=None,
+ url=None,
+ title = "---------",
+ channel = "---------",
+ thumbnails = (None, None),
+ channel_url = None,
+ loading_done = False,
+ length = 0,
+
+ bg_color=None,
+ fg_color=None,
+ text_color=None,
+ theme_color=None,
+ hover_color=None,
+ special_color=None):
+
+ super().__init__(master=master, border_width=border_width, corner_radius=8,
+ fg_color=fg_color, bg_color=bg_color, height=height, width=width)
+
+ self.loading_done = loading_done
+ self.url = url
+ self.title = title
+ self.channel = channel
+ self.thumbnails = thumbnails
+ self.supported_download_types = ["..........", "..........", ".........."]
+
+ self.channel_url = channel_url
+ self.length = length
+ self.theme = "-"
+ self.fg_color = fg_color
+ self.text_color = text_color
+ self.height = height
+ self.theme_color = theme_color
+ self.hover_color = hover_color
+ self.special_color = special_color
+
+ self.killed = False
+
+
+ self.create_widgets()
+ self.place_widgets()
+ threading.Thread(target=self.RunThemeTracker).start()
+ self.set_video_data()
+
+
+ def create_widgets(self):
+ self.thumbnail_btn = tk.Button(master=self,
+ bd=0,
+ font=("arial", 14, "bold"),
+ bg=self.getColorBasedOnTheme(self.fg_color),
+ relief="sunken",
+ state="disabled",
+ cursor="hand2",
+ command=lambda:webbrowser.open(self.url)
+ )
+
+ self.len_label = ctk.CTkLabel(master=self, width=1, height=1,
+ text_color=self.text_color,
+ fg_color=self.fg_color,
+ bg_color=self.fg_color,
+ font=("arial", 11, "bold"))
+
+ self.title_label = tk.Label(master=self ,anchor="w",
+ text = "Title : ",
+ font=('arial',10,'normal'),
+ bg=self.getColorBasedOnTheme(self.fg_color),
+ fg=self.getColorBasedOnTheme(self.text_color)
+ )
+
+ self.channel_label = tk.Button(master=self ,font=('arial',9,'bold'),
+ anchor="w",
+ text = "Channel : ",
+ bd=0,
+ bg=self.getColorBasedOnTheme(self.fg_color),
+ fg=self.getColorBasedOnTheme(self.text_color),
+ command=lambda:webbrowser.open(self.channel_url),
+ relief="sunken",
+ state="disabled",
+ cursor="hand2",
+
+ )
+
+ self.url_label = tk.Label(master=self ,anchor="w",
+ bg=self.getColorBasedOnTheme(self.fg_color),
+ text=self.url, font=('arial',10,"italic underline"),
+ )
+
+ self.remove_btn = ctk.CTkButton(master=self,
+ command=self.kill,
+ text="X",
+ font=("arial", 12, "bold"),
+ fg_color=self.fg_color,
+ bg_color=self.fg_color,
+ border_width=2,
+ border_color=self.special_color,
+ text_color=self.special_color,
+ width=12, height=10,
+ border_spacing=0,
+ hover=False,
+ )
+
+ self.bind("", self.configure_widget_sizes)
+
+
+ def set_theme(self):
+ self.configure(border_color=self.theme_color)
+
+ self.thumbnail_btn.configure(bg=self.getColorBasedOnTheme(self.fg_color),
+ fg=self.getColorBasedOnTheme(self.text_color),
+ disabledforeground=self.getColorBasedOnTheme(self.text_color),
+ activebackground=self.getColorBasedOnTheme(self.fg_color))
+
+ self.title_label.configure(bg=self.getColorBasedOnTheme(self.fg_color),
+ fg=self.getColorBasedOnTheme(self.text_color))
+
+ self.url_label.configure(bg=self.getColorBasedOnTheme(self.fg_color),
+ fg=self.theme_color)
+
+ self.channel_label.configure(bg=self.getColorBasedOnTheme(self.fg_color),
+ fg=self.getColorBasedOnTheme(self.text_color),
+ activebackground=self.getColorBasedOnTheme(self.fg_color),
+ activeforeground=self.theme_color,)
+
+
+ def getColorBasedOnTheme(self, color):
+ return getColor(color, self.theme)
+
+
+ def RunThemeTracker(self):
+ while True:
+ if ctk.get_appearance_mode() != self.theme:
+ self.theme = ctk.get_appearance_mode()
+ self.set_theme()
+ time.sleep(2)
+
+
+ def configure_widget_sizes(self, e):
+ self.title_label.place(width=self.winfo_width()-450)
+ self.url_label.place(width=self.winfo_width()-450)
+ self.channel_label.place(width=self.winfo_width()-450)
+
+
+ def place_widgets(self):
+ self.remove_btn.place(relx=1, x=-24, y=4)
+ self.thumbnail_btn.place(x=5, y=2, relheight=1, height=-4, width=int((self.height-4)/9*16))
+ self.len_label.place(rely=1, y=-10, x=117, anchor="e")
+ self.title_label.place(x=130, y=4, height=20)
+ self.channel_label.place(x=130, y=24, height=20)
+ self.url_label.place(x=130, y=44, height=20)
+
+
+ def set_video_data(self):
+ self.title_label.configure(text="Title : "+self.title)
+ self.channel_label.configure(text="Channel : "+self.channel)
+ self.url_label.configure(text=self.url)
+ self.len_label.configure(text=convertTime(self.length))
+ if self.loading_done:
+ self.thumbnail_btn.configure(image=self.thumbnails[0], text="")
+ def thumbnail_hover_img_set(e):
+ self.thumbnail_btn.configure(image=self.thumbnails[1], text="")
+ def thumbnail_img_set(e):
+ self.thumbnail_btn.configure(image=self.thumbnails[0], text="")
+ self.thumbnail_btn.bind("",thumbnail_hover_img_set)
+ self.thumbnail_btn.bind("",thumbnail_img_set)
+ self.len_label.bind("",thumbnail_hover_img_set)
+ self.len_label.bind("",thumbnail_img_set)
+
+
+ def kill(self):
+ for child_widget in self.winfo_children():
+ for sub_child_widget in child_widget.winfo_children():
+ sub_child_widget.destroy()
+ child_widget.destroy()
+ self.pack_forget()
+ self.destroy()
+
+
+ def set_new_theme(self, new_theme_color):
+ self.theme_color = new_theme_color
+ self.set_theme()
\ No newline at end of file
diff --git a/widgets/addedPlayList.py b/widgets/addedPlayList.py
new file mode 100644
index 0000000..f23fff2
--- /dev/null
+++ b/widgets/addedPlayList.py
@@ -0,0 +1,218 @@
+from .playList import playList
+import pytube
+from .addedVideo import addedVideo
+import threading
+import time
+import customtkinter as ctk
+
+class addedPlayList(playList):
+ def __init__(self,
+ master=None,
+ width=None,
+ height=None,
+ border_width=None,
+
+ playlist_url=None,
+ channel_url = None,
+ playlist_title = "---------",
+ channel = "---------",
+
+ download_btn_command = None,
+ video_download_btn_command = None,
+ bg_color=None,
+ fg_color=None,
+ text_color=None,
+ theme_color=None,
+ hover_color=None,
+ special_color=None,
+ ):
+
+ super().__init__(
+ master=master,
+ height=height,
+ width=width,
+ border_width=border_width,
+
+ channel_url = channel_url,
+ playlist_url=playlist_url,
+ playlist_title=playlist_title,
+ channel=channel,
+
+ bg_color=bg_color,
+ fg_color=fg_color,
+ text_color=text_color,
+ theme_color=theme_color,
+ hover_color=hover_color,
+ special_color=special_color,
+ )
+
+ self.removed_count = 0
+ self.download_btn_command = download_btn_command
+ self.video_download_btn_command = video_download_btn_command
+ self.videos_thumbnails = []
+ self.videos = []
+ threading.Thread(target=self.get_playlist_data).start()
+
+
+ def create_widgets(self):
+ super().create_widgets()
+ self.info_frame = ctk.CTkFrame(master=self.playlist_info_widget,
+ height=self.height-4,
+ width=270,
+ bg_color=self.fg_color,
+ fg_color=self.fg_color)
+ self.resolutions_box = ctk.CTkComboBox(master=self.info_frame, values=["..........", "..........", ".........."])
+
+ #β© β€
+ self.download_btn = ctk.CTkButton(master=self.info_frame, text="Download",
+ width=80, height=25,
+ border_width=2,
+ fg_color=self.fg_color,
+ bg_color=self.fg_color,
+ hover_color=self.hover_color,
+ text_color=self.text_color,
+ state="disabled",
+ command=lambda:self.download_btn_command(self))
+
+ self.status_label = ctk.CTkLabel(master=self.info_frame,
+ text="Loading",
+ height=15,
+ font=("arial", 13, "bold"),
+ bg_color=self.fg_color,
+ fg_color=self.fg_color,
+ )
+
+ self.reload_btn = ctk.CTkButton(self.playlist_info_widget ,text="β³",
+ width=15 ,height=15,
+ font=("arial", 22, "normal"),
+ command = self.reload_playlist,
+ fg_color=self.fg_color,
+ bg_color=self.fg_color,
+ hover=False,
+ )
+
+
+ def set_theme(self):
+ super().set_theme()
+ self.download_btn.configure(border_color=self.theme_color)
+ if self.status_label.cget("text") != "Failed":
+ self.status_label.configure(text_color=self.theme_color)
+ self.reload_btn.configure(text_color=self.theme_color)
+ for video in self.videos:
+ video.set_new_theme(self.theme_color)
+
+
+ def place_widgets(self):
+ super().place_widgets()
+ self.info_frame.place(y=2, relx=1, x=-330)
+ self.resolutions_box.place(y=25,x=0)
+ self.download_btn.place(x=160 ,y=12)
+ self.status_label.place(x=200, anchor="n", y=52)
+
+
+ def reload_playlist(self):
+ self.reload_btn.place_forget()
+ self.status_label.configure(text_color=self.theme_color, text="Loading")
+ if len(self.videos) != 0:
+ for video in self.videos:
+ if video.loading_failed:
+ video.reload_video()
+ else:
+ threading.Thread(target=self.get_playlist_data).start()
+
+
+ def get_playlist_data(self):
+ self.view_btn.configure(state="disabled")
+ try:
+ self.playlist = pytube.Playlist(self.playlist_url)
+ self.video_count = self.playlist.length
+ self.channel = self.playlist.owner
+ self.playlist_title = self.playlist.title
+ self.channel_url = self.playlist.owner_url
+ self.view_btn.configure(state="normal")
+ self.set_playlist_data()
+ self.get_videos_data()
+ except:
+ self.set_loading_failed()
+
+
+ def get_videos_data(self):
+ for video_url in (self.playlist.video_urls):
+ self.videos.append(
+ addedVideo(master=self.playlist_item_frame,
+ height=70,
+ width=self.playlist_item_frame.winfo_width()-20,
+ fg_color=self.fg_color,
+ bg_color=self.bg_color,
+ theme_color = self.theme_color,
+ text_color=self.text_color,
+ hover_color=self.hover_color,
+ special_color=self.special_color,
+ border_width=1,
+ url=video_url, download_btn_command=self.video_download_btn_command)
+ )
+ self.videos[-1].remove_btn.configure(command=lambda video = self.videos[-1]: self.remove_video(video))
+ self.videos[-1].pack(fill="x", padx=(20,0), pady=1)
+ threading.Thread(target=self.progress_track).start()
+
+
+ def progress_track(self):
+ videos = self.videos.copy()
+ loaded = 0
+ while True:
+ load_fails = False
+ for video in videos:
+ if video not in self.videos:
+ videos.remove(video)
+ continue
+ if video.loading_failed:
+ self.set_loading_failed()
+ load_fails = True
+ elif video.loading_done:
+ loaded += 1
+ videos.remove(video)
+ if loaded == len(self.videos):
+ break
+ if not load_fails:
+ self.reload_btn.place_forget()
+ self.status_label.configure(text="Loading", text_color=self.theme_color)
+ time.sleep(1)
+ self.set_loading_done()
+
+
+ def set_loading_failed(self):
+ self.status_label.configure(text="Failed", text_color=self.special_color)
+ self.reload_btn.place(relx=1, y=32, x=-80)
+
+
+ def set_loading_done(self):
+ self.status_label.configure(text="Loaded")
+ self.download_btn.configure(state="normal")
+ self.resolutions_box.configure(values=["Highest Quality", "Lowest Quality", "Audio Only"])
+ self.resolutions_box.set("Highest Quality")
+ self.resolutions_box.configure(command=self.select_download_option)
+
+
+ def select_download_option(self, e):
+ if e=="Highest Quality":
+ index = 0
+ elif e=="Lowest Quality":
+ index = -2
+ elif e=="Audio Only":
+ index = -1
+ for video in self.videos:
+ video.resolutions_box.set(video.resolutions_box.cget("values")[index])
+ video.select_download_option(video.resolutions_box.cget("values")[index])
+
+
+ def remove_video(self, video):
+ self.videos.remove(video)
+ self.complete_count_label.configure(text=len(self.videos))
+ self.video_count -= 1
+ video.pack_forget()
+ video.kill()
+
+ def kill(self):
+ for video in self.videos:
+ video.kill()
+ super().kill()
\ No newline at end of file
diff --git a/widgets/addedVideo.py b/widgets/addedVideo.py
new file mode 100644
index 0000000..29974db
--- /dev/null
+++ b/widgets/addedVideo.py
@@ -0,0 +1,222 @@
+from .Video import Video
+import customtkinter as ctk
+from functions.getThumbnails import getThumbnails
+from functions.passIt import passIt
+from functions.getSupportedDownloadTypes import getSupportedDownloadTypes
+from functions.sortDict import sortDict
+from functions.formatToComboBoxValues import formatToComboBoxValues
+import time
+import pytube
+import threading
+
+
+class addedVideo(Video):
+ dot_count = 1
+
+ waiting_started = False
+ loading_configure_started = False
+
+ simultaneous_loading = 0
+ max_simultaneous_loading = 1
+
+ waiting_added_videos = []
+
+ def configure_loading():
+ def loading():
+ while True:
+ addedVideo.dot_count += 1
+ if addedVideo.dot_count > 4:
+ addedVideo.dot_count = 1
+ time.sleep(0.7)
+ threading.Thread(target=loading).start()
+
+ def waiting_for_loading():
+ def waiting():
+ while True:
+ if addedVideo.max_simultaneous_loading > addedVideo.simultaneous_loading and len(addedVideo.waiting_added_videos) > 0:
+ try:
+ addedVideo.waiting_added_videos[0].start_get_video_data()
+ except:
+ pass
+ addedVideo.waiting_added_videos.pop(0)
+ time.sleep(1)
+ threading.Thread(target=waiting).start()
+
+
+ def __init__(self, master,
+ border_width=None,
+ width=None,
+ height=None,
+ url=None,
+ download_btn_command=passIt,
+
+ bg_color=None,
+ fg_color=None,
+ text_color=None,
+ theme_color=None,
+ hover_color=None,
+ special_color=None,
+ ):
+
+ if not addedVideo.waiting_started:
+ addedVideo.waiting_started = True
+ addedVideo.waiting_for_loading()
+
+ if not addedVideo.loading_configure_started:
+ addedVideo.loading_configure_started = True
+ addedVideo.configure_loading()
+
+ self.loading_failed = False
+ self.loading_loop_running = True
+ self.download_btn_command = download_btn_command
+
+ super().__init__(master=master, border_width=border_width, theme_color=theme_color, hover_color=hover_color,
+ special_color=special_color, width=width,
+ fg_color=fg_color, bg_color=bg_color, height=height ,url=url, text_color=text_color)
+
+ threading.Thread(target=self.loading).start()
+
+ self.start_get_video_data()
+
+
+ def start_get_video_data(self):
+ if addedVideo.max_simultaneous_loading > addedVideo.simultaneous_loading :
+ addedVideo.simultaneous_loading += 1
+ self.status_label.configure(text="Loading")
+ threading.Thread(target=self.get_video_data).start()
+ else:
+ #print("Here")
+ addedVideo.waiting_added_videos.append(self)
+ self.status_label.configure(text="Waiting")
+
+
+ def loading(self):
+ while not self.loading_done and not self.loading_failed :
+ self.loading_loop_running = True
+ self.thumbnail_btn.configure(text="."*addedVideo.dot_count)
+ self.update()
+ time.sleep(0.7)
+ if not self.loading_failed:
+ self.set_video_data()
+ self.set_fetch_data()
+ self.loading_loop_running = False
+
+
+ def get_video_data(self):
+ try:
+ #print("Loading : ",addedVideo.simultaneous_loading)
+ self.video = pytube.YouTube(self.url)
+ self.title = self.video.title
+ self.channel = self.video.author
+ self.length = self.video.length
+ self.video_stream_data = self.video.streams
+ self.channel_url = self.video.channel_url
+ self.thumbnails = getThumbnails(self.video)
+ self.supported_download_types = sortDict(getSupportedDownloadTypes(self.video_stream_data))
+ self.loading_done = True
+ self.status_label.configure(text="Loaded")
+ addedVideo.simultaneous_loading -= 1
+ except Exception as error:
+ if self.killed is not True:
+ addedVideo.simultaneous_loading -= 1
+ self.set_loading_failed()
+
+
+ def select_download_option(self, e: str):
+ self.download_quality = e.replace(" ","").split("|")[0]
+ if "kbps" in self.download_quality:
+ self.download_type = "Audio"
+ else:
+ self.download_type = "Video"
+
+
+ def set_fetch_data(self):
+ self.resolutions_box.configure(values=formatToComboBoxValues(self.supported_download_types))
+ self.resolutions_box.set(self.resolutions_box.cget("values")[0])
+ self.select_download_option(self.resolutions_box.get())
+ self.resolutions_box.configure(command=self.select_download_option)
+ self.thumbnail_btn.configure(state="normal")
+ self.channel_label.configure(state="normal")
+ self.download_btn.configure(state="normal")
+
+
+ def create_widgets(self):
+ super().create_widgets()
+ self.info_frame = ctk.CTkFrame(master=self,
+ height=self.height-4,
+ width=250,
+ bg_color=self.fg_color,
+ fg_color=self.fg_color)
+ self.resolutions_box = ctk.CTkComboBox(master=self.info_frame, values=[".........."])
+
+ #β© β€
+ self.download_btn = ctk.CTkButton(master=self.info_frame, text="Download", width=80, height=25,
+ border_width=2,
+ fg_color=self.fg_color, bg_color=self.fg_color,
+ hover_color=self.hover_color,
+ text_color=self.text_color,
+ state="disabled",
+ command=lambda:self.download_btn_command(self))
+
+ self.status_label = ctk.CTkLabel(master=self.info_frame,
+ text="",
+ height=15,
+ text_color=self.theme_color,
+ font=("arial", 12, "bold"),
+ bg_color=self.fg_color,
+ fg_color=self.fg_color,
+ )
+
+ self.reload_btn = ctk.CTkButton(self ,text="β³",
+ width=15 ,height=15,
+ font=("arial", 20, "normal"),
+ command = self.reload_video,
+ fg_color=self.fg_color,
+ bg_color=self.fg_color,
+ hover=False,
+ )
+ # β― βΊ β» β΅ βΈ β·
+
+ def set_theme(self):
+ super().set_theme()
+ self.download_btn.configure(border_color=self.theme_color)
+ if self.status_label.cget("text") != "Failed":
+ self.status_label.configure(text_color=self.theme_color)
+ self.reload_btn.configure(text_color=self.theme_color)
+
+
+ def place_widgets(self):
+ super().place_widgets()
+ self.info_frame.place(y=2, relx=1, x=-350)
+ self.resolutions_box.place(y=15,x=20)
+ self.download_btn.place(x=170 ,y=8)
+ self.status_label.place(x=210, anchor="n", y=44)
+
+
+ def set_loading_failed(self):
+ self.loading_failed = True
+ while self.loading_loop_running:
+ time.sleep(1)
+ self.master.master.master.update()
+ self.thumbnail_btn.configure(text="...",
+ disabledforeground=self.special_color)
+ self.status_label.configure(text_color=self.special_color,
+ text="Failed")
+ self.reload_btn.place(relx=1, y=22, x=-80)
+
+
+ def reload_video(self):
+ self.loading_failed = False
+ self.reload_btn.place_forget()
+ self.thumbnail_btn.configure(disabledforeground=self.getColorBasedOnTheme(self.text_color))
+ self.status_label.configure(text_color=self.theme_color, text="Loading")
+ threading.Thread(target=self.loading).start()
+ self.start_get_video_data()
+
+ def kill(self):
+ self.killed = True
+ if self in addedVideo.waiting_added_videos:
+ addedVideo.waiting_added_videos.remove(self)
+ elif self.loading_done is not True:
+ addedVideo.simultaneous_loading -= 1
+ super().kill()
\ No newline at end of file
diff --git a/widgets/colorBtn.py b/widgets/colorBtn.py
new file mode 100644
index 0000000..847f9c1
--- /dev/null
+++ b/widgets/colorBtn.py
@@ -0,0 +1,53 @@
+import customtkinter as ctk
+
+
+class ColorBtn(ctk.CTkButton):
+ def __init__(self,
+ master=None,
+ height=1,
+ width=1,
+ fg_color=None,
+ bg_color=None,
+ corner_radius=0,
+ hover=False,
+ text="",
+ font=None,
+ text_color=None,
+ border_width=0,
+ ):
+ super().__init__(master=master, height=height, width=width, fg_color=fg_color,border_width=border_width,text_color=text_color,
+ bg_color=bg_color, corner_radius=corner_radius, hover=hover, text=text, font=font)
+ self.height = height
+ self.width = width
+ self.bind("", self.react)
+ self.bind("", self.react_back)
+
+
+ def react(self, e):
+ self.configure(width=self.cget("width")+4,
+ height=self.cget("height")+4)
+ self.grid(pady=8, padx=2)
+
+
+ def react_back(self, e):
+ self.configure(width=self.cget("width")-4,
+ height=self.cget("height")-4)
+ self.grid(pady=10, padx=4)
+
+
+ def set_clicked(self):
+ self.configure(state="disabled")
+ self.unbind("")
+ self.unbind("")
+ self.set_unclicked_before_btn()
+
+
+ def set_unclicked_before_btn(self):
+ for widget in self.master.winfo_children():
+ if type(widget) == ColorBtn and widget!=self:
+ if widget.cget("width") > self.width:
+ widget.bind("", widget.react)
+ widget.bind("", widget.react_back)
+ widget.configure(width=self.width, height=self.height)
+ widget.grid(pady=10, padx=4)
+ widget.configure(state="normal")
\ No newline at end of file
diff --git a/widgets/downloadedPlayList.py b/widgets/downloadedPlayList.py
new file mode 100644
index 0000000..72cb4fe
--- /dev/null
+++ b/widgets/downloadedPlayList.py
@@ -0,0 +1,94 @@
+import tkinter as tk
+from .playList import playList
+from .downloadingPlayList import downloadingPlayList
+from .downloadedVideo import downloadedVideo
+from .downloadingVideo import downloadingVideo
+
+class downloadedPlayList(playList):
+ def __init__(self,
+ master=None,
+ width=None,
+ height=None,
+ border_width=None,
+
+ bg_color=None,
+ fg_color=None,
+ text_color=None,
+ theme_color=None,
+ hover_color=None,
+ special_color=None,
+
+ channel_url = None,
+ playlist_url=None,
+ playlist_title = "---------",
+ channel = "---------",
+ video_count = "?",
+
+ videos=None,
+ ):
+ super().__init__(master=master,
+ width=width,
+ height=height,
+ border_width=border_width,
+
+ bg_color=bg_color,
+ fg_color=fg_color,
+ text_color=text_color,
+ theme_color=theme_color,
+ hover_color=hover_color,
+ special_color=special_color,
+
+ channel_url=channel_url,
+ playlist_url=playlist_url,
+ playlist_title=playlist_title,
+ channel=channel,
+ video_count=video_count,)
+
+ self.videos: list[downloadingVideo] = videos
+ self.downloaded_videos = []
+ self.create_downloaded_widgets()
+
+ def create_downloaded_widgets(self):
+ for video in self.videos:
+ self.downloaded_videos.append(
+ downloadedVideo(master=self.playlist_item_frame,
+ height=70,
+ border_width=1,
+ width=self.playlist_item_frame.winfo_width()-20,
+ download_quality=video.download_quality,
+ download_type=video.download_type,
+
+ fg_color=self.fg_color,
+ bg_color=self.bg_color,
+ text_color=self.text_color,
+ hover_color=self.hover_color,
+ theme_color=self.theme_color,
+ special_color=self.special_color,
+
+ thumbnails=video.thumbnails,
+ title=video.title,
+ channel=video.channel,
+ channel_url=video.channel_url,
+ url=video.url,
+ download_path=video.download_file_name,
+ file_size=video.total_bytes,
+ length=video.length,
+ )
+ )
+
+ self.downloaded_videos[-1].remove_btn.configure(command = lambda video=self.downloaded_videos[-1]: self.remove_video(video))
+ self.downloaded_videos[-1].pack(fill="x", padx=(20,0), pady=1)
+
+ self.view_btn.configure(state="normal")
+
+
+ def set_theme(self):
+ super().set_theme()
+ for video in self.downloaded_videos:
+ video.set_new_theme(self.theme_color)
+
+
+ def remove_video(self, video: downloadingVideo):
+ self.video_count -= 1
+ self.complete_count_label.configure(text=self.video_count)
+ video.kill()
\ No newline at end of file
diff --git a/widgets/downloadedVideo.py b/widgets/downloadedVideo.py
new file mode 100644
index 0000000..9901ec2
--- /dev/null
+++ b/widgets/downloadedVideo.py
@@ -0,0 +1,89 @@
+from .Video import Video
+import customtkinter as ctk
+import os
+from functions.getConvertedSize import getConvertedSize
+
+class downloadedVideo(Video):
+ def __init__(self, master,
+ border_width=None,
+ height=None,
+ width=None,
+ download_quality=None,
+ download_type=None,
+
+ title=None,
+ channel=None,
+ thumbnails=(None,None),
+ loading_done = True,
+ url=None,
+ channel_url=None,
+ file_size=0,
+ download_path="",
+ length=None,
+
+ bg_color=None,
+ fg_color=None,
+ text_color=None,
+ theme_color=None,
+ hover_color=None,
+ special_color=None
+ ):
+
+ self.download_path = download_path
+ self.file_size=file_size
+ self.download_quality = download_quality
+ self.download_type = download_type
+ super().__init__(master=master, border_width=border_width, theme_color=theme_color,hover_color=hover_color,
+ channel_url=channel_url,special_color=special_color,width=width, length=length,
+ fg_color=fg_color, bg_color=bg_color, height=height ,url=url, text_color=text_color,
+ thumbnails=thumbnails, title=title, channel=channel, loading_done=loading_done)
+ self.set_state()
+
+
+ def create_widgets(self):
+ super().create_widgets()
+ self.download_type_label = ctk.CTkLabel(master=self,
+ text=self.download_type + " : "+ self.download_quality,
+ fg_color=self.fg_color,
+ height=15,
+ font=("arial", 12, "bold"),
+ text_color=self.text_color, bg_color=self.fg_color)
+ self.file_size_label = ctk.CTkLabel(master=self,
+ text=getConvertedSize(self.file_size,2) , fg_color=self.fg_color,
+ font=("arial", 12, "normal"),
+ height=15,
+ text_color=self.text_color, bg_color=self.fg_color)
+ #ππππΏπ
+ self.download_path_btn = ctk.CTkButton(master=self,
+ text="π",
+ font=("arial", 30, "bold"),
+ cursor="hand2",
+ command=lambda:os.startfile("\\".join(self.download_path.split("\\")[0:-1])),
+ hover=False,
+ bg_color=self.fg_color,
+ fg_color=self.fg_color, height=15,width=30)
+
+
+ def set_theme(self):
+ super().set_theme()
+ self.download_path_btn.configure(text_color=self.theme_color)
+
+
+ def place_widgets(self):
+ super().place_widgets()
+ self.download_type_label.place(y=14)
+ self.file_size_label.place(y=40)
+ self.download_path_btn.place(y=12)
+
+
+ def configure_widget_sizes(self, e):
+ super().configure_widget_sizes(e)
+ self.download_type_label.place(x=self.winfo_width()-300)
+ self.file_size_label.place(x=self.winfo_width()-300)
+ self.download_path_btn.place(x=self.winfo_width()-150)
+
+
+ def set_state(self):
+ self.thumbnail_btn.configure(state="normal")
+ self.channel_label.configure(state="normal")
+ self.thumbnail_btn.configure(command=lambda:os.startfile(self.download_path))
\ No newline at end of file
diff --git a/widgets/downloadingPlayList.py b/widgets/downloadingPlayList.py
new file mode 100644
index 0000000..632a8c5
--- /dev/null
+++ b/widgets/downloadingPlayList.py
@@ -0,0 +1,222 @@
+import customtkinter as ctk
+import tkinter as tk
+import pytube
+import time
+import threading
+from functions.getColor import getColor
+from .playList import playList
+from .downloadingVideo import downloadingVideo
+import webbrowser
+
+class downloadingPlayList(playList):
+ def __init__(self,
+ master=None,
+ width=None,
+ height=None,
+ border_width=None,
+
+ channel_url = None,
+ playlist_url=None,
+ playlist_title = "---------",
+ channel = "---------",
+ video_count = "?",
+
+ bg_color=None,
+ fg_color=None,
+ text_color=None,
+ theme_color=None,
+ hover_color=None,
+ special_color=None,
+
+ videos: list[downloadingVideo] = None,
+ download_directory = "",
+ downloaded_callback_function = None
+ ):
+
+ super().__init__(master=master,
+ height=height,
+ width=width,
+ border_width=border_width,
+
+ channel_url = channel_url,
+ playlist_url=playlist_url,
+ playlist_title=playlist_title,
+ channel=channel,
+ video_count=video_count,
+
+ bg_color=bg_color,
+ fg_color=fg_color,
+ text_color=text_color,
+ theme_color=theme_color,
+ hover_color=hover_color,
+ special_color=special_color,
+ )
+
+ self.downloaded_callback_function = downloaded_callback_function
+ self.videos = videos
+ self.download_directory = download_directory
+ self.downloading_videos = []
+ threading.Thread(target=self.download_videos).start()
+
+ def create_widgets(self):
+ super().create_widgets()
+ self.info_frame = ctk.CTkFrame(self, height=self.height-4, bg_color=self.fg_color, fg_color=self.fg_color)
+
+ self.download_progress_bar = ctk.CTkProgressBar(master=self.info_frame,
+ bg_color=self.fg_color,
+ height=8)
+
+ self.download_percentage_label = ctk.CTkLabel(master=self.info_frame,
+ text="",
+ font=("arial", 12, "bold"),
+ bg_color=self.fg_color,
+ fg_color=self.fg_color,
+ text_color=self.text_color)
+
+ self.status_label = ctk.CTkLabel(master=self.info_frame,
+ text="",
+ font=("arial", 12, "bold"),
+ bg_color=self.fg_color,
+ fg_color=self.fg_color,
+ )
+
+ self.redownload_btn = ctk.CTkButton(self ,text="β³",
+ width=15 ,height=15,
+ font=("arial", 20,"normal"),
+ command = self.redownload_video,
+ fg_color=self.fg_color,
+ bg_color=self.fg_color,
+ hover=False,
+ )
+
+ def set_theme(self):
+ super().set_theme()
+ self.redownload_btn.configure(text_color=self.theme_color)
+ for video in self.downloading_videos:
+ video.set_new_theme(self.theme_color)
+
+
+ def configure_widget_sizes(self, e):
+ self.info_frame.configure(width=self.winfo_width()/2 - 150)
+
+ def place_widgets(self):
+ super().place_widgets()
+
+ self.title_label.place(x=50, y=10, height=20, width=-50, relwidth=0.5)
+ self.channel_label.place(x=50, y=34, height=20, width=-50, relwidth=0.5)
+ self.url_label.place(x=50, y=54, height=20, width=-50, relwidth=0.5)
+
+ self.info_frame.place(relx=0.5, y=2, x=50)
+ self.download_percentage_label.place(relx=0.5, anchor="n", y=12)
+ self.download_percentage_label.configure(height=20)
+ self.download_progress_bar.place(relwidth=1, y=40)
+ self.status_label.place(relx=0.775, anchor="n", y=55)
+ self.status_label.configure(height=20)
+
+
+ def redownload_video(self):
+ self.redownload_btn.place_forget()
+ for video in self.downloading_videos:
+ if video.download_failed:
+ video.redownload_video()
+
+
+ def download_videos(self):
+ for video in self.videos:
+ self.downloading_videos.append(
+ downloadingVideo(
+ master=self.playlist_item_frame,
+ height=70,
+ width=self.playlist_item_frame.winfo_width()-20,
+ border_width=1,
+
+ fg_color=self.fg_color,
+ bg_color=self.bg_color,
+ theme_color=self.theme_color,
+ text_color=self.text_color,
+ hover_color=self.hover_color,
+ special_color=self.special_color,
+
+ channel_url=video.channel_url,
+ url=video.url,
+ download_quality=video.download_quality,
+ download_type=video.download_type,
+ title=video.title,
+ channel=video.channel,
+ thumbnails=video.thumbnails,
+ video_stream_data=video.video_stream_data,
+ length=video.length,
+
+ download_directory = self.download_directory,
+ downloaded_callback_function = None
+ )
+ )
+ self.downloading_videos[-1].pack(fill="x", padx=(20,0), pady=1)
+ self.downloading_videos[-1].remove_btn.configure(command=lambda video = self.downloading_videos[-1]: self.remove_video(video))
+ self.view_btn.configure(state="normal")
+ threading.Thread(target=self.progress_track).start()
+
+
+ def progress_track(self):
+ downloading_videos = self.downloading_videos.copy()
+ while True:
+ download_complete = True
+ download_fails = False
+ downloaded_count = 0
+ for video in downloading_videos:
+ if video not in self.downloading_videos:
+ downloading_videos.remove(video)
+ continue
+ if video.download_failed:
+ download_complete = False
+ download_fails = True
+ elif video.download_completed:
+ downloaded_count += 1
+ #downloading_videos.remove(video)
+ else:
+ download_complete = False
+
+ self.set_progress(downloaded_count/self.video_count)
+
+ if not download_fails:
+ self.set_download_runnning()
+ if download_fails:
+ self.set_download_failed()
+ elif download_complete:
+ break
+ time.sleep(1)
+ self.set_downloading_done()
+
+
+ def set_progress(self, progress):
+ self.download_progress_bar.set(progress)
+ self.download_percentage_label.configure(text = str(round(progress*100, 2)) + " %")
+
+
+ def set_download_failed(self):
+ self.redownload_btn.place(relx=1, y=32, x=-80)
+ self.status_label.configure(text="Failed", text_color=self.special_color)
+
+
+ def set_downloading_done(self):
+ self.status_label.configure(text="Downloaded", text_color=self.theme_color)
+ self.downloaded_callback_function(self)
+ self.kill()
+
+
+ def set_download_runnning(self):
+ self.redownload_btn.place_forget()
+ self.status_label.configure(text="Downloading", text_color=self.theme_color)
+
+ def remove_video(self, video):
+ self.downloading_videos.remove(video)
+ self.complete_count_label.configure(text=len(self.downloading_videos))
+ self.video_count -= 1
+ video.pack_forget()
+ video.kill()
+
+
+ def kill(self):
+ for video in self.downloading_videos:
+ video.kill()
+ super().kill()
\ No newline at end of file
diff --git a/widgets/downloadingVideo.py b/widgets/downloadingVideo.py
new file mode 100644
index 0000000..8b9704b
--- /dev/null
+++ b/widgets/downloadingVideo.py
@@ -0,0 +1,339 @@
+from .Video import Video
+import customtkinter as ctk
+import pytube
+import threading
+import time
+from pytube import request as pytube_request
+from functions.passIt import passIt
+from functions.getConvertedSize import getConvertedSize
+from functions.getValidFileName import getValidFileName
+from functions.removeInvalidCharts import removeInvalidChars
+from functions.createDownloadDirectory import createDownloadDirectory
+
+
+class downloadingVideo(Video):
+ waiting_started = False
+ simultaneous_downloading = 0
+ max_simultaneous_downloading = 1
+ waiting_downloading_videos = []
+ def waiting_for_downloading():
+ def waiting():
+ while True:
+ if downloadingVideo.max_simultaneous_downloading > downloadingVideo.simultaneous_downloading and len(downloadingVideo.waiting_downloading_videos) > 0:
+ try:
+ downloadingVideo.waiting_downloading_videos[0].start_download_video()
+ except:
+ pass
+ downloadingVideo.waiting_downloading_videos.pop(0)
+ time.sleep(1)
+ threading.Thread(target=waiting).start()
+
+ def __init__(self, master,
+ border_width=None,
+ width=None,
+ height=None,
+ download_quality=None,
+ download_type=None,
+ title=None,
+ channel=None,
+ thumbnails=(None,None),
+ loading_done = True,
+ video_stream_data: pytube.StreamQuery = None,
+ url=None,
+ channel_url=None,
+ length = None,
+
+ bg_color=None,
+ fg_color=None,
+ text_color=None,
+ theme_color=None,
+ hover_color=None,
+ special_color=None,
+
+ downloaded_callback_function=None,
+ download_directory = ""):
+
+ if not downloadingVideo.waiting_started:
+ downloadingVideo.waiting_started = True
+ downloadingVideo.waiting_for_downloading()
+
+ self.download_completed = False
+ self.download_failed = False
+ self.download_pause_req = False
+ self.downloaded_callback_function = downloaded_callback_function
+ self.download_quality = download_quality
+ self.download_type = download_type
+ self.video_stream_data = video_stream_data
+ self.download_directory = download_directory
+ super().__init__(master=master, border_width=border_width, theme_color=theme_color,hover_color=hover_color,
+ channel_url=channel_url, width=width, length=length,
+ fg_color=fg_color, bg_color=bg_color, height=height ,url=url, text_color=text_color,
+ thumbnails=thumbnails, title=title, channel=channel, loading_done=loading_done, special_color=special_color)
+ self.set_state()
+ threading.Thread(target=self.start_download_video).start()
+
+
+ def create_widgets(self):
+ super().create_widgets()
+
+ self.info_frame = ctk.CTkFrame(self, height=self.height-4, bg_color=self.fg_color, fg_color=self.fg_color)
+
+ self.download_progress_bar = ctk.CTkProgressBar(master=self.info_frame,
+ bg_color=self.fg_color,
+ height=8)
+
+ self.download_progress_label = ctk.CTkLabel(master=self.info_frame,
+ text="",
+ font=("arial", 12, "bold"),
+ bg_color=self.fg_color,
+ fg_color=self.fg_color,
+ text_color=self.text_color)
+
+ self.download_percentage_label = ctk.CTkLabel(master=self.info_frame,
+ text="",
+ font=("arial", 12, "bold"),
+ bg_color=self.fg_color,
+ fg_color=self.fg_color,
+ text_color=self.text_color)
+
+ self.download_type_label = ctk.CTkLabel(master=self.info_frame,
+ text="",
+ font=("arial", 12, "normal"),
+ bg_color=self.fg_color,
+ fg_color=self.fg_color,
+ text_color=self.text_color)
+
+ self.net_speed_label = ctk.CTkLabel(master=self.info_frame,
+ text="",
+ font=("arial", 12, "normal"),
+ bg_color=self.fg_color,
+ fg_color=self.fg_color,
+ text_color=self.text_color)
+
+ self.status_label = ctk.CTkLabel(master=self.info_frame,
+ text="",
+ font=("arial", 12, "bold"),
+ bg_color=self.fg_color,
+ fg_color=self.fg_color,
+ )
+
+ self.redownload_btn = ctk.CTkButton(self ,text="β³",
+ width=15 ,height=15,
+ font=("arial", 20,"normal"),
+ command = self.redownload_video,
+ fg_color=self.fg_color,
+ bg_color=self.fg_color,
+ hover=False,
+ )
+ # β― βΊ β» β΅ βΈ β·
+ self.pause_resume_button = ctk.CTkButton(self ,text="βΈ",
+ width=15 ,height=15,
+ font=("arial", 20,"normal"),
+ command = self.pause_downloading,
+ fg_color=self.fg_color,
+ bg_color=self.fg_color,
+ hover=False,
+ )
+
+
+ def place_widgets(self):
+ self.remove_btn.place(relx=1, x=-24, y=4)
+ self.thumbnail_btn.place(x=5, y=2, relheight=1, height=-4, width=int((self.height-4)/9*16))
+ self.title_label.place(x=130, y=4, height=20, relwidth=0.5, width=-150)
+ self.channel_label.place(x=130, y=24, height=20, relwidth=0.5, width=-150)
+ self.url_label.place(x=130, y=44, height=20, relwidth=0.5, width=-150)
+ self.len_label.place(rely=1, y=-10, x=117, anchor="e")
+ #self.pause_resume_button.place(y=22, relx=1, x=-80)
+ self.info_frame.place(relx=0.5, y=2)
+
+ self.download_progress_label.place(relx=0.25, anchor="n", y=4)
+ self.download_progress_label.configure(height=20)
+ self.download_percentage_label.place(relx=0.75, anchor="n", y=4)
+ self.download_percentage_label.configure(height=20)
+ self.download_progress_bar.place(relwidth=1, y=30)
+ self.download_type_label.place(relx=0.115, anchor="n", y=40)
+ self.download_type_label.configure(height=20)
+ self.net_speed_label.place(relx=0.445, anchor="n", y=40)
+ self.net_speed_label.configure(height=20)
+ self.status_label.place(relx=0.775, anchor="n", y=40)
+ self.status_label.configure(height=20)
+
+ def set_theme(self):
+ super().set_theme()
+ self.download_progress_bar.configure(progress_color=self.theme_color)
+ if self.status_label.cget("text") != "Failed":
+ self.status_label.configure(text_color=self.theme_color)
+ self.redownload_btn.configure(text_color=self.theme_color)
+ self.pause_resume_button.configure(text_color=self.theme_color)
+
+ def start_download_video(self):
+ self.download_completed = False
+ self.download_failed = False
+ if downloadingVideo.max_simultaneous_downloading > downloadingVideo.simultaneous_downloading :
+ downloadingVideo.simultaneous_downloading += 1
+ try:
+ threading.Thread(target=self.download_video).start()
+ self.set_pause_btn()
+ self.pause_resume_button.place(y=22, relx=1, x=-80)
+ self.net_speed_label.configure(text="0.0 B/s")
+ self.download_progress_bar.set(0)
+ self.download_percentage_label.configure(text="0.0 %")
+ self.set_status("Downloading")
+ except:
+ downloadingVideo.simultaneous_downloading -= 1
+ else:
+ self.set_waiting()
+ downloadingVideo.waiting_downloading_videos.append(self)
+
+ def redownload_video(self):
+ self.redownload_btn.place_forget()
+ self.start_download_video()
+
+
+ def set_download_failed(self):
+ self.download_failed = True
+ if self.killed is not True:
+ downloadingVideo.simultaneous_downloading -= 1
+ self.pause_resume_button.place_forget()
+ self.redownload_btn.place(y=22, relx=1, x=-80)
+
+ def set_waiting(self):
+ self.pause_resume_button.place_forget()
+ self.download_progress_bar.set(0.5)
+ self.download_percentage_label.configure(text="")
+ self.net_speed_label.configure(text="")
+ self.download_progress_label.configure(text="")
+ self.download_type_label.configure(text="")
+ self.set_status("Waiting")
+
+ def configure_widget_sizes(self, e):
+ self.info_frame.configure(width=self.winfo_width()/2-100)
+
+ def set_state(self):
+ self.thumbnail_btn.configure(state="normal")
+ self.channel_label.configure(state="normal")
+
+ def set_status(self, status):
+ if status=="Failed":
+ self.status_label.configure(text_color=self.special_color)
+ else:
+ self.status_label.configure(text_color=self.theme_color)
+ self.status_label.configure(text=status)
+
+
+ def download_video(self):
+ try:
+ createDownloadDirectory(self.download_directory)
+ except BufferError:
+ pass
+
+ self.downloaded_bytes = 0
+ self.download_file_name = self.download_directory + "\\" + removeInvalidChars(self.channel + " - " + self.title)
+ try:
+ self.download_type_label.configure(text=self.download_type + " : "+self.download_quality)
+ if self.download_type == "Video":
+ stream = self.video_stream_data.get_by_resolution(self.download_quality)
+ self.download_file_name += ".mp4"
+ else:
+ stream = self.video_stream_data.get_audio_only()
+ self.download_file_name += ".mp3"
+ self.total_bytes = stream.filesize
+ self.converted_total_bytes = getConvertedSize(self.total_bytes,2)
+ self.download_file_name = getValidFileName(self.download_file_name)
+ self.set_download_progress()
+ except Exception as error:
+ self.set_download_failed()
+ self.set_status("Failed")
+
+ print(self.download_file_name)
+ try:
+ with open(self.download_file_name,"wb") as self.video_file :
+ stream = pytube_request.stream(stream.url)
+ count = 0
+ while 1:
+ try:
+ time_s = time.time()
+ if self.download_pause_req:
+ if count == 0:
+ self.pause_resume_button.configure(command=self.resume_downloading)
+ self.set_status("Paused")
+ self.set_resume_btn()
+ count = 1
+ time.sleep(0.3)
+ continue
+ self.pause_resume_button.configure(command=self.pause_downloading)
+ count = 0
+ chunk = next(stream, None)
+ time_e = time.time()
+ if chunk:
+ self.video_file.write(chunk)
+ self.net_speed_label.configure(text=getConvertedSize(((self.downloaded_bytes + len(chunk)) - self.downloaded_bytes)/(time_e-time_s),1)+"/s")
+ self.downloaded_bytes += len(chunk)
+ self.set_download_progress()
+ else:
+ if self.downloaded_bytes == self.total_bytes:
+ self.set_download_complete()
+ break
+ else:
+ self.set_download_failed()
+ self.set_status("Failed")
+ break
+ except Exception as error:
+ self.set_download_failed()
+ self.set_status("Failed")
+ break
+ except Exception as error:
+ self.set_download_failed()
+ self.set_status("Failed")
+
+
+ def set_resume_btn(self):
+ # β― βΊ β» β΅ βΈ β·
+ self.pause_resume_button.configure(text="β·")
+
+
+ def set_pause_btn(self):
+ # β― βΊ β» β΅ βΈ β·
+ self.pause_resume_button.configure(text="βΈ")
+
+
+ def pause_downloading(self):
+ self.pause_resume_button.configure(command=passIt)
+ self.set_status("Pausing")
+ self.download_pause_req = True
+
+
+ def resume_downloading(self):
+ self.pause_resume_button.configure(command=passIt)
+ self.set_status("Downloading")
+ self.download_pause_req = False
+ self.set_pause_btn()
+
+
+ def set_download_progress(self):
+ percentage = self.downloaded_bytes/self.total_bytes
+ self.download_progress_bar.set(percentage)
+ self.download_percentage_label.configure(text=str(round(percentage*100,2))+" %")
+ self.download_progress_label.configure(text=f"{getConvertedSize(self.downloaded_bytes,2)} / {self.converted_total_bytes}")
+
+
+ def set_download_complete(self):
+ self.set_status("Downloaded")
+ self.download_completed = True
+ downloadingVideo.simultaneous_downloading -= 1
+ try:
+ if self.downloaded_callback_function is not None:
+ self.downloaded_callback_function(self)
+ self.kill()
+ except Exception as error:
+ print(error)
+
+
+ def kill(self):
+ self.killed = True
+ if self in downloadingVideo.waiting_downloading_videos:
+ downloadingVideo.waiting_downloading_videos.remove(self)
+ elif self.download_completed is not True:
+ downloadingVideo.simultaneous_downloading -= 1
+ super().kill()
+
diff --git a/widgets/playList.py b/widgets/playList.py
new file mode 100644
index 0000000..f37b22e
--- /dev/null
+++ b/widgets/playList.py
@@ -0,0 +1,214 @@
+import customtkinter as ctk
+import tkinter as tk
+import pytube
+import time
+import threading
+from functions.getColor import getColor
+import webbrowser
+
+class playList(ctk.CTkFrame):
+ def __init__(self,
+ master=None,
+ width=None,
+ height=None,
+ border_width=None,
+
+ channel_url = None,
+ playlist_url=None,
+ playlist_title = "---------",
+ channel = "---------",
+ video_count = "?",
+
+ bg_color=None,
+ fg_color=None,
+ text_color=None,
+ theme_color=None,
+ hover_color=None,
+ special_color=None,
+ ):
+
+ super().__init__(master=master, fg_color=fg_color)
+
+ self.height = height
+ self.width = width
+ self.border_width = border_width
+
+ self.channel_url = channel_url
+ self.channel = channel
+
+ self.playlist_url = playlist_url
+
+ self.playlist_title =playlist_title
+
+ self.fg_color = fg_color
+ self.bg_color = bg_color
+ self.theme = "-"
+ self.theme_color = theme_color
+ self.text_color = text_color
+ self.special_color = special_color
+ self.hover_color = hover_color
+ self.video_count = video_count
+
+ self.create_widgets()
+ self.place_widgets()
+ self.set_playlist_data()
+ threading.Thread(target=self.RunThemeTracker).start()
+
+
+ def create_widgets(self):
+ self.playlist_info_widget = ctk.CTkFrame(master=self, border_width=self.border_width,
+ fg_color=self.fg_color,
+ bg_color=self.bg_color,
+ height=85)
+
+ self.view_btn = ctk.CTkButton(master=self.playlist_info_widget,
+ font=('arial', 18, 'bold'),
+ text = ">",
+ width=1,
+ height=1,
+ bg_color=self.fg_color,
+ fg_color=self.fg_color,
+ hover=False,
+ command=self.view_videos,
+ state="disabled",
+ cursor="hand2",
+ )
+ """
+ self.thumbnail_btn = tk.Button(master=self.playlist_info_widget,
+ bd=0,
+ font=("arial", 14, "bold"),
+ bg=self.getColorBasedOnTheme(self.bg_color),
+ relief="sunken",
+ state="disabled",
+ cursor="hand2",
+ command=lambda:webbrowser.open(self.playlist_url)
+ )
+ """
+ self.title_label = tk.Label(master=self.playlist_info_widget,
+ anchor="w",
+ text = "Title : ",
+ font=('arial',10,'bold'),
+ bg=self.getColorBasedOnTheme(self.fg_color),
+ fg=self.getColorBasedOnTheme(self.text_color)
+ )
+
+ self.channel_label = tk.Button(master=self.playlist_info_widget,
+ font=('arial',9,'bold'),
+ anchor="w",
+ text = "Channel : ",
+ bd=0,
+ bg=self.getColorBasedOnTheme(self.fg_color),
+ fg=self.getColorBasedOnTheme(self.text_color),
+ command=lambda:webbrowser.open(self.channel_url),
+ relief="sunken",
+ state="disabled",
+ cursor="hand2",
+ )
+
+ self.url_label = tk.Label(master=self.playlist_info_widget ,anchor="w",
+ bg=self.getColorBasedOnTheme(self.fg_color),
+ text=self.playlist_url,
+ font=('arial',11,"italic underline"),
+ )
+
+ self.remove_btn = ctk.CTkButton(master=self.playlist_info_widget,
+ command=self.kill,
+ text="X",
+ font=("arial", 10, "bold"),
+ fg_color=self.fg_color,
+ bg_color=self.fg_color,
+ border_width=2,
+ border_color=self.special_color,
+ text_color=self.special_color,
+ width=12, height=10,
+ border_spacing=0,
+ hover=False,
+ )
+
+ self.complete_count_label = ctk.CTkLabel(master=self.playlist_info_widget,
+ width=15 ,height=15,
+ font=("arial", 13, "normal"),
+ fg_color=self.fg_color,
+ bg_color=self.fg_color,
+ justify="right")
+
+ self.playlist_item_frame = ctk.CTkFrame(self, fg_color=self.fg_color)
+
+ self.bind("", self.configure_widget_sizes)
+
+ def getColorBasedOnTheme(self, color):
+ return getColor(color, self.theme)
+
+
+ def RunThemeTracker(self):
+ while True:
+ if ctk.get_appearance_mode() != self.theme:
+ self.theme = ctk.get_appearance_mode()
+ self.set_theme()
+ time.sleep(2)
+
+
+ def set_theme(self):
+ self.configure(border_color=self.theme_color)
+
+ self.playlist_info_widget.configure(border_color=self.theme_color)
+ #self.thumbnail_btn.configure(bg=self.getColorBasedOnTheme(self.fg_color),
+ # fg=self.getColorBasedOnTheme(self.text_color),
+ # disabledforeground=self.getColorBasedOnTheme(self.text_color),
+ # activebackground=self.getColorBasedOnTheme(self.fg_color))
+ self.view_btn.configure(text_color=self.theme_color)
+
+ self.title_label.configure(bg=self.getColorBasedOnTheme(self.fg_color),
+ fg=self.getColorBasedOnTheme(self.text_color))
+
+ self.url_label.configure(bg=self.getColorBasedOnTheme(self.fg_color),
+ fg=self.theme_color)
+
+ self.channel_label.configure(bg=self.getColorBasedOnTheme(self.fg_color),
+ fg=self.getColorBasedOnTheme(self.text_color),
+ activebackground=self.getColorBasedOnTheme(self.fg_color),
+ activeforeground=self.theme_color,)
+
+ self.complete_count_label.configure(text_color=self.theme_color)
+
+
+ def place_widgets(self):
+ self.playlist_info_widget.pack(fill="x")
+ self.view_btn.place(y=55, x=10)
+ #self.thumbnail_btn.place(x=25, y=2, relheight=1, height=-4, width=int((self.height-4)/9*16))
+ self.title_label.place(x=50, y=10, height=20, width=-420, relwidth=1)
+ self.channel_label.place(x=50, y=34, height=20, width=-420, relwidth=1)
+ self.url_label.place(x=50, y=54, height=20, width=-420, relwidth=1)
+ self.complete_count_label.place(relx=1, x=-60, rely=1, y=-25)
+ self.remove_btn.place(relx=1, x=-23, y=4)
+
+
+ def configure_widget_sizes(self, e):
+ pass
+
+
+ def hide_videos(self):
+ self.view_btn.configure(command=self.view_videos, text=">", font=('arial', 18, 'bold'))
+ self.playlist_item_frame.pack_forget()
+
+
+ def view_videos(self):
+ self.view_btn.configure(command=self.hide_videos, text="V", font=('arial', 13, 'bold'))
+ self.playlist_item_frame.pack(padx=10, fill="x", pady=2)
+
+
+ def set_playlist_data(self):
+ self.complete_count_label.configure(text=f"{self.video_count}")
+ self.title_label.configure(text="Title : "+self.playlist_title)
+ self.channel_label.configure(text="Channel : "+self.channel)
+ self.url_label.configure(text=self.playlist_url)
+
+
+ def kill(self):
+ self.pack_forget()
+ self.destroy()
+
+
+ def set_new_theme(self, new_theme_color):
+ self.theme_color = new_theme_color
+ self.set_theme()
\ No newline at end of file
diff --git a/widgets/settingPanel.py b/widgets/settingPanel.py
new file mode 100644
index 0000000..3547677
--- /dev/null
+++ b/widgets/settingPanel.py
@@ -0,0 +1,146 @@
+import customtkinter as ctk
+from .colorBtn import ColorBtn
+from functions.saveSettings import saveSettings
+from functions.getThemeSettings import getThemeSettings
+from functions.getGeneralSettings import getGeneralSettings
+from functions.getConvertedPath import getConvertedPath
+import os
+
+class settingPanel(ctk.CTkFrame):
+
+ def __init__(self,master=None,
+ fg_color=None,
+ bg_color=None,
+ text_color=None,
+ theme_color_change_call_back=None,
+ directory_change_call_back=None):
+
+ self.theme_color_change_call_back = theme_color_change_call_back
+ self.theme_settings_file = "./settings/theme.json"
+ self.theme_settings = getThemeSettings()
+
+ self.directory_change_call_back = directory_change_call_back
+ self.general_settings_file = "./settings/general.json"
+ self.general_settings = getGeneralSettings()
+
+
+
+ super().__init__(master=master, fg_color=fg_color, bg_color=bg_color)
+
+
+ self.color_scheme_frame = ctk.CTkFrame(master=self, fg_color=fg_color, bg_color=bg_color,
+ border_width=2)
+ self.color_scheme_frame.place(x=10, y=50,)
+
+ self.color_scheme_label = ctk.CTkLabel(master=self.color_scheme_frame, fg_color=fg_color, bg_color=bg_color,
+ font=("arial", 14, "bold"), text="Color Scheme : ", height=20)
+ self.color_scheme_label.grid(row=0, columnspan=len(self.theme_settings["default_theme_colors"])+2, pady=10)
+
+ ctk.CTkLabel(master=self.color_scheme_frame, fg_color=fg_color, bg_color=bg_color, text="").grid(row=1, column=0, padx=4)
+ col_ = 1
+ for app_theme_color in self.theme_settings["default_theme_colors"]:
+ btn = ColorBtn(master=self.color_scheme_frame, width=30, height=30, hover=False,
+ fg_color=app_theme_color, bg_color=fg_color, corner_radius=10, text='')
+ btn.configure(command=lambda btn_=btn,color=app_theme_color,:self.save_theme_color(btn_, color))
+ btn.grid(row=1, column=col_, padx=4, pady=10)
+ if app_theme_color == self.theme_settings["app_theme_color"]:
+ btn.react("E")
+ btn.set_clicked()
+ btn.set_unclicked_before_btn()
+ col_ += 1
+ ctk.CTkLabel(master=self.color_scheme_frame, fg_color=fg_color, bg_color=bg_color, text="").grid(row=1, column=col_, padx=4)
+
+
+
+ self.theme_mode_frame = ctk.CTkFrame(master=self, fg_color=fg_color, bg_color=bg_color,
+ border_width=2)
+ self.theme_mode_frame.place(x=420, y=50)
+ self.color_mode_label = ctk.CTkLabel(master=self.theme_mode_frame, fg_color=fg_color, bg_color=bg_color,
+ font=("arial", 14, "bold"), text="Theme : ", height=20)
+ self.color_mode_label.grid(row=0, columnspan=5, pady=10)
+ ctk.CTkLabel(master=self.theme_mode_frame, fg_color=fg_color, bg_color=bg_color, text="").grid(row=1, column=0, padx=4)
+ col_ = 1
+ for theme_mode in ["System", "Dark", "Light"]:
+ btn = ColorBtn(master=self.theme_mode_frame, width=60, height=30, hover=False, border_width=2, text_color=text_color,
+ fg_color=fg_color, bg_color=bg_color, corner_radius=6, text=theme_mode, font=("arial", 12, "bold"))
+ btn.configure(command=lambda btn_=btn,color=theme_mode,:self.save_theme_mode(btn_, color))
+ btn.grid(row=1, column=col_, padx=4, pady=10)
+ if theme_mode == self.theme_settings["app_theme_mode"]:
+ btn.react("E")
+ btn.set_clicked()
+ btn.set_unclicked_before_btn()
+ col_ += 1
+ ctk.CTkLabel(master=self.theme_mode_frame, fg_color=fg_color, bg_color=bg_color, text="").grid(row=1, column=4, padx=4)
+
+
+ self.download_path_frame = ctk.CTkFrame(master=self, fg_color=fg_color, bg_color=bg_color,
+ border_width=2)
+ self.download_path_frame.place(x=10, y=200)
+
+ self.download_path_label = ctk.CTkLabel(master=self.download_path_frame, fg_color=fg_color, bg_color=bg_color, width=200,
+ font=("arial", 14, "bold"), text="Download Directory : ", height=40)
+ self.download_path_label.grid(row=0, column=0, pady=10, padx=10)
+
+ self.download_path_btn = ctk.CTkButton(master=self.download_path_frame,
+ text="π",
+ font=("arial", 25, "bold"),
+ cursor="hand2",
+ hover=False,
+ command=self.get_download_directory,
+ bg_color=fg_color,
+ fg_color=fg_color, height=40,width=40)
+ self.download_path_btn.grid(row=0, column=1, pady=10, padx=10)
+ self.download_path_entry = ctk.CTkEntry(master=self.download_path_frame, fg_color=fg_color, bg_color=bg_color, width=610,
+ font=("arial", 13, "underline"), height=40)
+ self.download_path_entry.insert(0, self.general_settings["download_directory"] if self.general_settings["download_directory"]!=False else f"C:\\Users\\{os.getlogin()}\\Downloads\\PyTube Downloader\\")
+ self.download_path_entry.grid(row=1, columnspan=2, pady=10, padx=10)
+ self.configure_theme_color()
+
+
+ def save_theme_color(self,btn: ColorBtn, theme_color):
+ if self.theme_settings["app_theme_color"] != theme_color:
+ self.theme_settings["app_theme_color"] = theme_color
+ btn.set_clicked()
+ btn.set_unclicked_before_btn()
+ self.theme_settings["app_theme_color"] = theme_color
+ saveSettings(self.theme_settings_file,self.theme_settings)
+ self.configure_theme_color()
+ self.theme_color_change_call_back(theme_color)
+
+
+ def save_theme_mode(self,btn: ColorBtn, theme):
+ btn.set_clicked()
+ btn.set_unclicked_before_btn()
+ self.theme_settings["app_theme_mode"] = theme
+ saveSettings(self.theme_settings_file,self.theme_settings)
+ self.configure_theme_color()
+ ctk.set_appearance_mode(theme)
+
+
+ def configure_theme_color(self):
+ self.color_scheme_frame.configure(border_color=self.theme_settings["app_theme_color"])
+ self.color_scheme_label.configure(text_color=self.theme_settings["app_theme_color"])
+
+ self.theme_mode_frame.configure(border_color=self.theme_settings["app_theme_color"])
+ self.color_mode_label.configure(text_color=self.theme_settings["app_theme_color"])
+
+ for btn in self.theme_mode_frame.winfo_children():
+ if type(btn) == ColorBtn:
+ btn.configure(border_color=self.theme_settings["app_theme_color"], text_color=self.theme_settings["app_theme_color"])
+
+ self.download_path_frame.configure(border_color=self.theme_settings["app_theme_color"])
+ self.download_path_label.configure(text_color=self.theme_settings["app_theme_color"])
+ self.download_path_btn.configure(text_color=self.theme_settings["app_theme_color"])
+ self.download_path_entry.configure(border_color=self.theme_settings["app_theme_color"], text_color=self.theme_settings["app_theme_color"])
+
+
+ def get_download_directory(self):
+ directory = ctk.filedialog.askdirectory()
+ if directory != "" and self.general_settings["download_directory"] != directory:
+ directory = getConvertedPath(directory)
+ self.general_settings["download_directory"] = directory
+ saveSettings(self.general_settings_file,self.general_settings)
+ self.download_path_entry.delete(0, "end")
+ self.download_path_entry.insert(0, directory)
+ self.directory_change_call_back(directory)
+
\ No newline at end of file