youtube.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. # This is a youtube-dl youtube song downloader. It uses both youtube-dl
  2. # for actual download and the invidious API for fetching info about the
  3. # song. GPLv3+
  4. import os
  5. from subprocess import *
  6. import json
  7. import random
  8. import urllib.request
  9. import urllib.parse
  10. from yt_dlp import YoutubeDL
  11. dfolder = os.path.expanduser("~/.matrix_music")
  12. youtubes = ["music.youtube.com", # That has to be in front, for strip function to work properly.
  13. "youtube.com", # Since in there is "youtube.com" in "music.youtube.com"
  14. "youtu.be"]
  15. invidious = ["invidious.namazso.eu",
  16. "yewtu.be",
  17. "invidious.snopyta.org",
  18. "invidious.kavin.rocks",
  19. "inv.riverside.rocks",
  20. "yt.artemislena.eu",
  21. "invidious.flokinet.to",
  22. "invidious.esmailelbob.xyz",
  23. "inv.bp.projectsegfau.lt",
  24. "y.com.sb",
  25. "invidious.tiekoetter.com",
  26. "invidious.nerdvpn.de",
  27. "invidious.slipfox.xyz",
  28. "watch.thekitty.zone",
  29. "inv.odyssey346.dev",
  30. "invidious.baczek.me",
  31. "invidious.weblibre.org",
  32. "invidious.privacydev.net",
  33. "yt.funami.tech"
  34. ]
  35. other_links = ["c7hqkpkpemu6e7emz5b4vyz7idjgdvgaaa3dyimmeojqbgpea3xqjoid.onion",
  36. "w6ijuptxiku4xpnnaetxvnkc5vqcdu7mgns2u77qefoixi63vbvnpnqd.onion",
  37. "kbjggqkzv65ivcqj6bumvp337z6264huv5kpkwuv6gu5yjiskvan7fad.onion",
  38. "grwp24hodrefzvjjuccrkw3mjq4tzhaaq32amf33dzpmuxe7ilepcmad.onion",
  39. "osbivz6guyeahrwp2lnwyjk2xos342h4ocsxyqrlaopqjuhwn2djiiyd.onion",
  40. "u2cvlit75owumwpy4dj2hsmvkq7nvrclkpht7xgyye2pyoxhpmclkrad.onion",
  41. "euxxcnhsynwmfidvhjf6uzptsmh4dipkmgdmcmxxuo7tunp3ad2jrwyd.onion",
  42. "invidious.esmail5pdn24shtvieloeedh7ehz3nrwcdivnfhfcedl7gf4kwddhkqd.onion",
  43. "inv.vernccvbvyi5qhfzyqengccj7lkove6bjot2xhh5kajhwvidqafczrad.onion",
  44. "am74vkcrjp2d5v36lcdqgsj2m6x36tbrkhsruoegwfcizzabnfgf5zyd.onion",
  45. "ng27owmagn5amdm7l5s3rsqxwscl5ynppnis5dqcasogkyxcfqn7psid.onion",
  46. "iv.odysfvr23q5wgt7i456o5t3trw2cw5dgn56vbjfbq2m7xsc5vqbqpcyd.onion",
  47. "invidious.g4c3eya4clenolymqbpgwz3q3tawoxw56yhzk4vugqrl6dtu3ejvhjid.onion",
  48. "verni6dr4qxjgjumnvesxerh5rvhv6oy5ddeibaqy5d7tgbiiyfa.b32.i2p",
  49. "vid.priv.au",
  50. "invidious.lidarshield.cloud",
  51. "invidious.silur.me",
  52. "iv.melmac.space",
  53. "iv.ggtyler.dev",
  54. "invidious.epicsite.xyz",
  55. "invidious.sethforprivacy.com",
  56. "yt.oelrichsgarcia.de",
  57. "invidious.drivet.xyz",
  58. "invidious.dhusch.de",
  59. "inv.vern.cc",
  60. "vid.puffyan.us"
  61. "piped.kavin.rocks",
  62. "viewtube.io"]
  63. all_instances = []
  64. for t in [youtubes, invidious, other_links]:
  65. for i in t:
  66. all_instances.append(i)
  67. class Logger(object):
  68. def debug(self, msg):
  69. pass
  70. def warning(self, msg):
  71. pass
  72. def error(self, msg):
  73. pass
  74. ydl_opts = {'logger': Logger()}
  75. def difference(x,y):
  76. if x > y:
  77. return x - y
  78. else:
  79. return y - x
  80. def fetch_playlist(playlistId):
  81. try:
  82. try:
  83. url = "https://invidious.namazso.eu/api/v1/playlists/"+playlistId
  84. req = urllib.request.Request(url, data=None)
  85. f = urllib.request.urlopen(req)
  86. data = json.loads(f.read().decode('utf-8'))
  87. except:
  88. with YoutubeDL(ydl_opts) as ydl:
  89. dump = ydl.extract_info(url=playlistId, download=False)
  90. with open('dump.json', 'w') as fp:
  91. json.dump(dump, fp, indent=4)
  92. # Converting youtube_dl data into Invidious type object
  93. data = {"videos":[]}
  94. for video in dump.get("entries", []):
  95. data["videos"].append({"videoId":video.get("id", "")})
  96. return data
  97. except Exception as e:
  98. print(" [ \033[31mFAILED\033[00m ] youtube_dl |", e)
  99. return str(e)
  100. def fetch_info(videoId):
  101. try:
  102. with YoutubeDL(ydl_opts) as ydl:
  103. dump = ydl.extract_info(url=videoId, download=False)
  104. with open('dump.json', 'w') as fp:
  105. json.dump(dump, fp, indent=4)
  106. # Converting youtube_dl data into Invidious type object
  107. data = {"lengthSeconds":dump.get("duration", 999999999999999),
  108. "title":dump.get("title", ""),
  109. "author":dump.get("channel", "")}
  110. return data
  111. except Exception as e:
  112. print(" [ \033[31mFAILED\033[00m ] youtube_dl |", e)
  113. for instance in invidious:
  114. try:
  115. url = "https://"+instance+"/api/v1/videos/"+videoId
  116. req = urllib.request.Request(url, data=None)
  117. f = urllib.request.urlopen(req)
  118. data = json.loads(f.read().decode('utf-8'))
  119. return data
  120. except Exception as e:
  121. print(" [ \033[31mFAILED\033[00m ] "+instance+" |", e)
  122. def search(query):
  123. # This function will search on youtube a query.
  124. for instance in invidious:
  125. try:
  126. url = "https://"+instance+"/api/v1/search/?q="+urllib.parse.quote_plus(query)
  127. req = urllib.request.Request(url, data=None)
  128. f = urllib.request.urlopen(req)
  129. data = json.loads(f.read().decode('utf-8'))
  130. return data
  131. except Exception as e:
  132. print(" [ \033[31mFAILED\033[00m ] "+instance+" |", e)
  133. def try_download(videoId, filename):
  134. # This fucntion will try download the song via youtube-dl
  135. #print("Trying to download", videoId)
  136. # Making sure that the download folder exists
  137. try:
  138. os.mkdir(dfolder)
  139. except:
  140. pass
  141. try:
  142. # We are using subbrocess.check_output here to catch errors.
  143. check_output(["yt-dlp",
  144. "https://youtube.com/watch?v="+videoId,
  145. "-x",
  146. "--output",
  147. dfolder+"/"+str(filename)+".%(ext)s"])
  148. for i in os.listdir(dfolder):
  149. if i.startswith(filename):
  150. return dfolder+"/"+i
  151. return dfolder+"/"+str(filename)
  152. except Exception as e:
  153. print(" [ \033[31mFAILED\033[00m ] youtube_dl |", e)
  154. return False
  155. def insure_song(videoId, filename):
  156. # This function will insure a song is downloaded.
  157. songInfo = fetch_info(videoId)
  158. #print("Song", songInfo["title"], "by", songInfo["author"], "direct attempt.")
  159. # First we will try downloading the song as is.
  160. d = try_download(videoId, filename)
  161. if d:
  162. #print("Worked!")
  163. return d
  164. else:
  165. #print("Failed! Trying similar...")
  166. # If it failed we are going to try downloading a similar song.
  167. titleblacklist = ["cover", "react", "review", "remix", "acoustic", "teaser", "live"]
  168. query = songInfo["title"]
  169. if " - Topic" in songInfo["author"]:
  170. query = songInfo["author"].replace(" - Topic", "")+" "+query
  171. others = search(query)
  172. for song in others:
  173. # Chekcing that it's a video
  174. if song["type"] != "video":
  175. continue
  176. # Checking if the song is a cover or a remix
  177. cont = False
  178. for i in titleblacklist:
  179. if i.lower() in song["title"].lower():
  180. cont = i
  181. break
  182. if cont:
  183. #print("Skipped:", song["title"], "because", cont )
  184. continue
  185. # Chekcing the length of the song to be roughly the original
  186. if difference(song["lengthSeconds"], songInfo["lengthSeconds"]) > 60:
  187. continue
  188. #print("Trying to download", song["title"])
  189. d = try_download(song["videoId"], filename)
  190. if d:
  191. return d
  192. return False
  193. def strip(url):
  194. """Function that strips youtube links. And leaves only the videoId."""
  195. videoId = url
  196. # Maybe it's a playlist
  197. if "list=" in videoId and "/watch?v=" not in videoId:
  198. videoId = videoId[videoId.find("list=")+5:]
  199. videoId = videoId.replace("http://", "").replace("https://", "").replace("www.", "")
  200. for i in all_instances:
  201. videoId = videoId.replace(i, "")
  202. videoId = videoId.replace("/watch?v=", "")
  203. if "&" in videoId:
  204. videoId = videoId[:videoId.find("&")]
  205. videoId = videoId.replace("/", "")
  206. return videoId