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.

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