You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

462 rivejä
19 KiB

  1. # get args
  2. # make torrent
  3. # read mediainfo
  4. # upload torrent
  5. # move torrent to watch dir
  6. # Standard library packages
  7. from subprocess import check_output
  8. import re
  9. import os
  10. import sys
  11. import argparse
  12. from urllib.parse import urlparse
  13. import json
  14. # Third-party packages
  15. from bs4 import BeautifulSoup
  16. from torf import Torrent
  17. from pathlib import Path
  18. # JPS-AU files
  19. import smpy
  20. from pymediainfo import MediaInfo
  21. def asciiart ():
  22. print("""
  23. ███████╗███╗ ███╗ █████╗ ██╗ ██╗ ████████╗██╗ ██╗
  24. ██╔════╝████╗ ████║ ██╔══██╗██║ ██║ ╚══██╔══╝██║ ██║
  25. ███████╗██╔████╔██║█████╗███████║██║ ██║█████╗██║ ██║ ██║
  26. ╚════██║██║╚██╔╝██║╚════╝██╔══██║██║ ██║╚════╝██║ ╚██╗ ██╔╝
  27. ███████║██║ ╚═╝ ██║ ██║ ██║╚██████╔╝ ██║ ╚████╔╝
  28. ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝
  29. """)
  30. def getargs():
  31. """
  32. Get arguments using argparse
  33. """
  34. parser = argparse.ArgumentParser()
  35. parser.add_argument('-i', '--input', help='Initiate upload on input file', nargs='?', required=True)
  36. parser.add_argument('-d', '--debug', help='Enable debug mode.', action='store_true')
  37. parser.add_argument("-dry", "--dryrun", help="Dryrun will carry out all actions other than the actual upload to SM.", action="store_true")
  38. parser.add_argument("-a", "--artists", help='Set the artists. (Romaji\English). Split multiple with ","', nargs='?')
  39. parser.add_argument("-oa", "--originalartist", help='Set the artist. (Original Language)', nargs='?')
  40. parser.add_argument("-ca", "--contributingartists", help='Set the contributing artists. (Romaji\English). Split multiple with ","', nargs='?')
  41. parser.add_argument("-ti", "--title", help='Set the title. (Romaji\English)', nargs='?')
  42. parser.add_argument("-oti", "--originaltitle", help='Set the title. (Original Language)', nargs='?')
  43. parser.add_argument("-des", "--description", help='Add a torrent description.', nargs='?', required=True)
  44. parser.add_argument("-t", "--tags", help="Add additional tags to the upload. At least 2 tags are required", nargs='?')
  45. parser.add_argument("-im", "--imageURL", help='Set the torrent cover URL.', nargs='?')
  46. parser.add_argument("-ms", "--mediasource", help='Set the media source.', nargs='?')
  47. parser.add_argument("-rt", "--releasetype", help='Set the release type.', nargs='?')
  48. parser.add_argument("-s", "--sub", help='Set the subtitle type.', nargs='?')
  49. parser.add_argument("-l", "--language", help='Set the language', nargs='?')
  50. parser.add_argument("-y", "--year", help='Set the torrent year (YYYYMMDD or YYYY).', nargs='?')
  51. parser.add_argument("-f", "--freeleech", help="Enables freeleech.", action="store_true")
  52. return parser.parse_args()
  53. def gatherdata():
  54. """
  55. Retrieve data about the upload. Ask for user input if necessary.
  56. :return: releasedata: dict
  57. """
  58. releasedata = {"submit": "true"}
  59. releasedata["album_desc"] = description
  60. if artists:
  61. releasedata['idols[]'] = artists
  62. else:
  63. input_english_artist = input("\n" + "_" * 100 + "\nEnter the romaji/english ARTIST name. Separate multiple with \",\". \n")
  64. input_english_artist = [x.strip() for x in input_english_artist.split(',')]
  65. releasedata['idols[]'] = input_english_artist
  66. if originalartist:
  67. releasedata['artist_jp'] = originalartist
  68. else:
  69. input_artist = input("\n" + "_" * 100 + "\nEnter the original ARTIST name. Press enter to skip if this torrent has multiple artists or artist name is already english. \n")
  70. releasedata['artist_jp'] = input_artist
  71. if contributingartists:
  72. input_english_contributing_artist = contributingartists
  73. else:
  74. input_english_contributing_artist = input("\n" + "_" * 100 + "\nEnter the romaji/english CONTRIBUTING ARTIST name. Separate with \",\". Press enter to skip.\n")
  75. if input_english_contributing_artist != "":
  76. input_english_contributing_artist = [x.strip() for x in input_english_contributing_artist.split(',')]
  77. releasedata['contrib_artists[]'] = input_english_contributing_artist
  78. if title:
  79. releasedata['title'] = title
  80. else:
  81. input_english_title = input("\n" + "_" * 100 + "\nEnter the romaji/english TITLE:\n")
  82. releasedata['title'] = input_english_title
  83. if originaltitle:
  84. releasedata['title_jp'] = originaltitle
  85. else:
  86. input_title = input("\n" + "_" * 100 + "\nEnter the original TITLE. Press enter to skip.\n\n")
  87. releasedata['title_jp'] = input_title
  88. if sub:
  89. releasedata["sub"] = sub
  90. else:
  91. while(True):
  92. input_sub = input("\n" + "_" * 100 + "\nEnter a number to choose subtitle type. \n1=NoSubs\n2=Softsubs\n3=Hardsubs\n")
  93. if input_sub == "1":
  94. releasedata["sub"] = "NoSubs"
  95. break
  96. elif input_sub == "2":
  97. releasedata["sub"] = "Softsubs"
  98. break
  99. elif input_sub == "3":
  100. releasedata["sub"] = "Hardsubs"
  101. break
  102. print("Invalid choice.")
  103. if language:
  104. releasedata["lang"] = language
  105. else:
  106. while(True):
  107. input_lang = input("\n" + "_" * 100 + "\nEnter a number to choose subtitle type. \n1=Japanese\n2=English\n3=Korean\n4=Chinese\n5=Vietnamese\n6=Other\n")
  108. if input_lang == "1":
  109. releasedata["lang"] = "Japanese"
  110. break
  111. elif input_lang == "2":
  112. releasedata["lang"] = "English"
  113. break
  114. elif input_lang == "3":
  115. releasedata["lang"] = "Korean"
  116. break
  117. elif input_lang == "2":
  118. releasedata["lang"] = "Chinese"
  119. break
  120. elif input_lang == "3":
  121. releasedata["lang"] = "Vietnamese"
  122. break
  123. elif input_lang == "2":
  124. releasedata["lang"] = "Other"
  125. break
  126. print("Invalid choice.")
  127. if mediasource:
  128. releasedata['media'] = mediasource
  129. else:
  130. while(True):
  131. input_lang = input("\n" + "_" * 100 + "\nEnter a number to choose the media source. \n1=HDTV\n2=Web\n")
  132. if input_lang == "1":
  133. releasedata["media"] = "HDTV"
  134. break
  135. elif input_lang == "2":
  136. releasedata["media"] = "Web"
  137. break
  138. print("Invalid choice.")
  139. if year:
  140. releasedata["year"] = year
  141. else:
  142. input_year = input("\n" + "_" * 100 + "\nEnter the year as YYYYMMDD or YYYY.\n")
  143. releasedata["year"] = input_year
  144. if tags:
  145. input_tags = tags
  146. else:
  147. input_tags = input("\n" + "_" * 100 + "\nEnter the tags. Separate multiple with \",\". Minimum 2 tags required.\n")
  148. if input_tags != "":
  149. input_tags = [x.strip() for x in input_tags.split(',')]
  150. releasedata["tags"] = input_tags
  151. list_of_types = ["PV", "Music Performance", "TV Music", "TV Variety", "TV-Drama"]
  152. if releasetype in list_of_types:
  153. if releasetype == "PV":
  154. releasedata["type"] = 5
  155. elif releasetype == "Music Performance":
  156. releasedata["type"] = 6
  157. elif releasetype == "TV Music":
  158. releasedata["type"] = 7
  159. elif releasetype == "TV Variety":
  160. releasedata["type"] = 8
  161. elif releasetype == "TV-Drama":
  162. releasedata["type"] = 9
  163. else:
  164. while(True):
  165. input_lang = input("\n" + "_" * 100 + "\nEnter a number to choose the upload type. \n1=PV\n2=Music Performance\n3=TV Music\n4=TV Variety\n5=TV-Drama\n")
  166. if input_lang == "1":
  167. releasedata["type"] = 5
  168. break
  169. elif input_lang == "2":
  170. releasedata["type"] = 6
  171. break
  172. elif input_lang == "3":
  173. releasedata["type"] = 7
  174. break
  175. elif input_lang == "4":
  176. releasedata["type"] = 8
  177. break
  178. elif input_lang == "5":
  179. releasedata["type"] = 9
  180. break
  181. print("Invalid choice.")
  182. return releasedata
  183. def add_mediainfo_to_releasedata(filename, releasedata):
  184. """
  185. Retrieve mediainfo and append it to the releasedata dictionary.
  186. :return: releasedata: dict
  187. """
  188. mediainfosall = ""
  189. media_info = MediaInfo.parse(filename)
  190. mediainfosall += str(MediaInfo.parse(filename, text=True))
  191. replacement = str(Path(filename).parent)
  192. mediainfosall = mediainfosall.replace(replacement, '')
  193. for track in media_info.tracks:
  194. if track.track_type == 'General':
  195. # releasedataout['language'] = track.audio_language_list # Will need to check if this is reliable
  196. if 'container' not in releasedata: # Not an ISO, only set container if we do not already know its an ISO
  197. releasedata['container'] = track.file_extension.upper()
  198. else: # We have ISO - get category data based Mediainfo if we have it
  199. if track.file_extension.upper() == 'VOB':
  200. releasedata['category'] = 'DVD'
  201. elif track.file_extension.upper() == 'M2TS': # Not used yet as we cannot handle Bluray / UDF
  202. releasedata['category'] = 'Bluray'
  203. if track.track_type == 'Video':
  204. validatecodec = {
  205. "MPEG Video": "MPEG-2",
  206. "AVC": "h264",
  207. "HEVC": "h265",
  208. "MPEG-4 Visual": "DivX", # MPEG-4 Part 2 / h263 , usually xvid / divx
  209. }
  210. for old, new in validatecodec.items():
  211. if track.format == old:
  212. releasedata['codec'] = new
  213. standardresolutions = {
  214. "3840": "1920",
  215. "1920": "1080",
  216. "1280": "720",
  217. "720": "480",
  218. }
  219. for width, height in standardresolutions.items():
  220. if str(track.width) == width and str(track.height) == height:
  221. releasedata['ressel'] = height
  222. if 'ressel' in releasedata.keys(): # Known resolution type, try to determine if interlaced
  223. if track.scan_type == "Interlaced" or track.scan_type == "MBAFF":
  224. releasedata['ressel'] += "i"
  225. else:
  226. releasedata['ressel'] += "p" # Sometimes a Progressive encode has no field set
  227. else: # Custom resolution
  228. releasedata['ressel'] = 'Other'
  229. releasedata['resolution'] = str(track.width) + "x" + str(track.height)
  230. if track.track_type == 'Audio' or track.track_type == 'Audio #1': # Handle multiple audio streams, we just get data from the first for now
  231. if track.format in ["AAC", "DTS", "PCM", "AC3"]:
  232. releasedata['audioformat'] = track.format
  233. elif track.format == "AC-3":
  234. releasedata['audioformat'] = "AC3"
  235. elif track.format == "MPEG Audio" and track.format_profile == "Layer 3":
  236. releasedata['audioformat'] = "MP3"
  237. elif track.format == "MPEG Audio" and track.format_profile == "Layer 2":
  238. releasedata['audioformat'] = "MP2"
  239. releasedata["mediainfo"] = mediainfosall
  240. return releasedata
  241. # Creates torrent file using torf module.
  242. def createtorrent(authkey, filepath, releasedata):
  243. """
  244. Creates a torrent.
  245. :param: authkey: authkey string
  246. :param: filepath: full path of the file for torrent creation
  247. :param: releasedata: dict
  248. :return: filename of created torrent
  249. """
  250. t = Torrent(path=filepath,
  251. trackers=[authkey]) # Torf requires we store authkeys in a list object. This makes it easier to add multiple announce urls.
  252. # Set torrent to private as standard practice for private trackers
  253. t.private = True
  254. t.source = "SugoiMusic"
  255. t.generate()
  256. ## Format releasedata to bring a suitable torrent name.
  257. # The reason we don't just use the directory name is because of an error in POSTING.
  258. # POSTS do not seem to POST hangul/jp characters alongside files.
  259. # filename = f"{releasedata['idols[]']} - {releasedata['title']} [{releasedata['media']}-{releasedata['audioformat']}].torrent"
  260. filename = f"{releasedata['title']}.torrent"
  261. filename = filename.replace("/","")
  262. try:
  263. t.write(filename)
  264. print("_" * 100)
  265. print("Torrent creation:\n")
  266. print(f"{filename} has been created.")
  267. except:
  268. print("_" * 100)
  269. print("Torrent creation:\n")
  270. os.remove(filename)
  271. print(f"{filename} already exists, existing torrent will be replaced.")
  272. t.write(filename)
  273. print(f"{filename} has been created.")
  274. return filename
  275. def getauthkey():
  276. """
  277. Get SM session authkey for use by uploadtorrent() data dict.
  278. Uses SM login data
  279. :return: authkey
  280. """
  281. smpage = sm.retrieveContent("https://sugoimusic.me/torrents.php?id=118") # Arbitrary page on JPS that has authkey
  282. soup = BeautifulSoup(smpage.text, 'html5lib')
  283. rel2 = str(soup.select('#content .thin .main_column .torrent_table tbody'))
  284. authkey = re.findall('authkey=(.*)&torrent_pass=', rel2)
  285. return authkey
  286. def uploadtorrent(torrent, imageURL, releasedata):
  287. """
  288. Uploads a torrent.
  289. :param: torrent: torrent filename.
  290. :param: imageURL: url to a cover image
  291. :param: releasedata: dict
  292. """
  293. # POST url.
  294. uploadurl = "https://sugoimusic.me/upload.php"
  295. # Dataset containing all of the information obtained from our FLAC files.
  296. data = releasedata
  297. data['image'] = imageURL
  298. if not dryrun:
  299. data['auth'] = authkey
  300. if debug:
  301. print('_' * 100)
  302. print('Release Data:\n')
  303. for field in data:
  304. print(field)
  305. print(data)
  306. try:
  307. postDataFiles = {
  308. 'file_input': open(torrent, 'rb')
  309. #'userfile': open(cover, 'rb')
  310. }
  311. except FileNotFoundError:
  312. print("_" * 100)
  313. print('File not found!\nPlease confirm file locations and names. Cover image or .torrent file could not be found')
  314. sys.exit()
  315. # If dryrun argument has not ben passed we will POST the results to JPopSuki.
  316. if dryrun != True:
  317. SMres = sm.retrieveContent(uploadurl, "post", data, postDataFiles)
  318. print('\nUpload POSTED. It may take a moment for this upload to appear on SugoiMusic.')
  319. SMerrorTorrent = re.findall('red; text-align: center;">(.*)</p>', SMres.text)
  320. SMerrorLogon = re.findall('<p>Invalid (.*)</p>', SMres.text)
  321. if SMerrorTorrent == None:
  322. print("Upload failed.")
  323. print(SMerrorTorrent)
  324. if SMerrorLogon == None:
  325. print(SMerrorLogon)
  326. ## TODO Filter through JPSres.text and create error handling based on responses
  327. #print(JPSres.text)
  328. def localfileorganization(torrent, watch_folder):
  329. if cfg['local_prefs']['add_to_watch_folder']:
  330. os.rename(torrent, f"{watch_folder}/{torrent}")
  331. if __name__ == "__main__":
  332. asciiart()
  333. args = getargs()
  334. # TODO consider calling args[] directly, we will then not need this line
  335. dryrun = debug = freeleech = imageURL = tags = inputfile = artists = contributingartists = title = None
  336. originalartist = originaltitle = description = sub = language = year = mediasource = releasetype = None
  337. inputfile = args.input
  338. description = args.description
  339. if args.dryrun:
  340. dryrun = True
  341. if args.debug:
  342. debug = True
  343. if args.freeleech:
  344. freeleech = True
  345. if args.imageURL:
  346. imageURL = args.imageURL
  347. if args.releasetype:
  348. releasetype = args.releasetype
  349. if args.title:
  350. title = args.title
  351. if args.artists:
  352. artists = args.artists
  353. if args.contributingartists:
  354. contributingartists = args.contributingartists
  355. if args.originalartist:
  356. originalartist = args.originalartist
  357. if args.originaltitle:
  358. originaltitle = args.originaltitle
  359. if args.language:
  360. language = args.language
  361. if args.year:
  362. year = args.year
  363. if args.sub:
  364. sub = args.sub
  365. if args.mediasource:
  366. mediasource = args.mediasource
  367. if args.tags:
  368. tags = args.tags
  369. releasedata = gatherdata()
  370. releasedata_and_mediainfo = add_mediainfo_to_releasedata(inputfile, releasedata)
  371. if debug:
  372. print("Release data and MediaInfo complete. Uploading torrent now.")
  373. with open(f'json_data/config.json') as f:
  374. cfg = json.load(f)
  375. loginData = {'username': cfg['credentials']['username'], 'password': cfg['credentials']['password']}
  376. loginUrl = "https://sugoimusic.me/login.php"
  377. loginTestUrl = "https://sugoimusic.me"
  378. successStr = "Enabled users"
  379. passkey = cfg['credentials']['passkey']
  380. annouceurl = "https://tracker.sugoimusic.me:24601/"+passkey+"/announce"
  381. # j is an object which can be used to make requests with respect to the loginsession
  382. sm = smpy.MyLoginSession(loginUrl, loginData, loginTestUrl, successStr, debug=args.debug)
  383. # Acquire authkey
  384. authkey = getauthkey()
  385. torrentfile = createtorrent(annouceurl, inputfile, releasedata_and_mediainfo)
  386. uploadtorrent(torrentfile, imageURL, releasedata_and_mediainfo)
  387. # Setting variable for watch/download folders
  388. ftp_watch_folder = cfg['ftp_prefs']['ftp_watch_folder']
  389. ftp_downloads_folder = cfg['ftp_prefs']['ftp_downloads_folder']
  390. local_watch_folder = cfg['local_prefs']['local_watch_folder']
  391. local_downloads_folder = cfg['local_prefs']['local_downloads_folder']
  392. if not dryrun:
  393. if cfg['local_prefs']['add_to_watch_folder'] or cfg['local_prefs']['add_to_downloads_folder']:
  394. localfileorganization(torrent=torrentfile, watch_folder=local_watch_folder)