Browse Source

initial commit

master
sharky555 2 years ago
commit
14b475ef2d
8 changed files with 1356 additions and 0 deletions
  1. +4
    -0
      .gitignore
  2. +63
    -0
      README.md
  3. +1
    -0
      copycommand.txt
  4. +117
    -0
      jpspy.py
  5. +22
    -0
      json_data/config.json.example
  6. +536
    -0
      json_data/dictionary.json
  7. +8
    -0
      requirements.txt
  8. +605
    -0
      upload.py

+ 4
- 0
.gitignore View File

@@ -0,0 +1,4 @@
__pycache__
config.json
*session.dat
*.torrent

+ 63
- 0
README.md View File

@@ -0,0 +1,63 @@
This project is **NOT** being maintained as i'd rather rewrite it from scratch.
## Overview
**JPS-AU-TV** is a tool for automating the uploading process on jpopsuki.eu.
**Features:**
- JPS Client.
- FTP Support
**Installation:**
- Install requirements
```
pip install -r requirements.txt
```
## Command Usage
```
python autoupload.py {command_name} {ID/URL}
```
Command | Description | Example
------------- | ------------- | -------------
-d, --debug | Provides additional information on upload for debugging purposes | `python autoupload.py -d`
-f, --freeleech | Enables freeleech (VIP+ Userclass Requirement) | `python autoupload.py -f -dir "Z:\Music\Korean\Ego\Ego - E [2020.01.02] [EP] [WEB-MP3]"`
-t, --tags | Add additional tags to upload, separated with comma | `python autoupload.py -t "korean, female.vocalist" -dir "Z:\Music\Korean\Ego\Ego - E [2020.01.02] [EP] [WEB-MP3]"`
-dry, --dryrun | Carries out all actions other than the upload itself.| `python autoupload.py -dir "Z:\Music\Korean\Ego\Ego - E [2020.01.02] [EP] [WEB-MP3]" -dry`
## Config.json
- It's not recommended to use both local watch/download folders and ftp watch/download folders at the same time as it will result in seeding from 2 locations.
**credentials:**
Config | Description | Example
------------- | ------------- | -------------
Username | JPopSuki Username | Slyy
Password | JPopSuki Password | Password
**local_prefs**
Config | Description | Example
------------- | ------------- | -------------
add_to_watch_folder | moves .torrent file to local watch folder | `true/false`
add_to_downloads_folder | moves torrent data to local downloads folder | `true/false`
local_watch_folder | directory of local watch folder | `Z:/watch/Transmission`
local_downloads_folder | directory of local downloads folder | `Z:/downloads`
**ftp_prefs:**
Config | Description | Example
------------- | ------------- | -------------
enable_ftp | enable ftp mode, if enabled suggested to disable local watch and downloads folders | `true/false`
add_to_watch_folder | transfer .torrent file to watch folder on FTP server | `true/false`
add_to_downloads_folder | transfer torrent data to downloads folder on FTP server | `true/false`
ftp_server | url of ftp server | haze.seedhost.eu
ftp_username | username of ftp account | slyy
ftp_password | password of ftp account | password
ftp_watch_folder | directory of ftp watch folder | `/downloads/watch/transmission`
ftp_downloads_folder | directory of ftp downloads folder | `/downloads`
## Disclaimer
- The usage of this script **may be** illegal in your country. It's your own responsibility to inform yourself of Copyright Law.

+ 1
- 0
copycommand.txt View File

@@ -0,0 +1 @@
python upload.py -i "C:\file.ts" --releasetype "PV" --artist "aimer" --title "test" --tags "pop" --year "2022" --mediasource "HDTV" --imagepath "C:\file.ts_thumbs_[2022.04.29_19.06.07].jpg" --torrentgroupdescription "a long description!!!!" --torrentdescription "placeholder"

+ 117
- 0
jpspy.py View File

@@ -0,0 +1,117 @@
import os
import pickle
import datetime
from urllib.parse import urlparse
import requests
class MyLoginSession:
def __init__(self,
loginUrl,
loginData,
loginTestUrl,
loginTestString,
sessionFileAppendix='_session.dat',
maxSessionTimeSeconds=30 * 60,
proxies=None,
userAgent='Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1',
debug=False,
forceLogin=False,
**kwargs):
"""
save some information needed to login the session
you'll have to provide 'loginTestString' which will be looked for in the
responses html to make sure, you've properly been logged in
'proxies' is of format { 'https' : 'https://user:pass@server:port', 'http' : ...
'loginData' will be sent as post data (dictionary of id : value).
'maxSessionTimeSeconds' will be used to determine when to re-login.
"""
urlData = urlparse(loginUrl)
self.proxies = proxies
self.loginData = loginData
self.loginUrl = loginUrl
self.loginTestUrl = loginTestUrl
self.maxSessionTime = maxSessionTimeSeconds
self.sessionFile = urlData.netloc + sessionFileAppendix
self.userAgent = userAgent
self.loginTestString = loginTestString
self.debug = debug
self.login(forceLogin, **kwargs)
def modification_date(self, filename):
"""
return last file modification date as datetime object
"""
t = os.path.getmtime(filename)
return datetime.datetime.fromtimestamp(t)
def login(self, forceLogin=False, **kwargs):
"""
login to a session. Try to read last saved session from cache file. If this fails
do proper login. If the last cache access was too old, also perform a proper login.
Always updates session cache file.
"""
wasReadFromCache = False
if self.debug:
print('loading or generating session...')
if os.path.exists(self.sessionFile) and not forceLogin:
time = self.modification_date(self.sessionFile)
# only load if file less than 30 minutes old
lastModification = (datetime.datetime.now() - time).seconds
if lastModification < self.maxSessionTime:
with open(self.sessionFile, "rb") as f:
self.session = pickle.load(f)
wasReadFromCache = True
if self.debug:
print("loaded session from cache (last access %ds ago) "
% lastModification)
if not wasReadFromCache:
self.session = requests.Session()
self.session.headers.update({'user-agent': self.userAgent})
res = self.session.post(self.loginUrl, data=self.loginData,
proxies=self.proxies, **kwargs)
if self.debug:
print('created new session with login')
self.saveSessionToCache()
# test login
res = self.session.get(self.loginTestUrl)
if res.text.lower().find(self.loginTestString.lower()) < 0:
if self.debug:
print(res.text)
raise Exception("could not log into provided site '%s'"
" (did not find successful login string)"
% self.loginUrl)
def saveSessionToCache(self):
"""
save session to a cache file
"""
# always save (to update timeout)
with open(self.sessionFile, "wb") as f:
pickle.dump(self.session, f)
if self.debug:
print('updated session cache-file %s' % self.sessionFile)
def retrieveContent(self, url, method="get", postData=None, postDataFiles=None, **kwargs):
"""
return the content of the url with respect to the session.
If 'method' is not 'get', the url will be called with 'postData'
as a post request.
"""
if method == 'get':
res = self.session.get(url, proxies=self.proxies, **kwargs)
else:
res = self.session.post(url, data=postData, proxies=self.proxies, files=postDataFiles, **kwargs)
# the session has been updated on the server, so also update in cache
self.saveSessionToCache()
return res

+ 22
- 0
json_data/config.json.example View File

@@ -0,0 +1,22 @@
{
"credentials": {
"username": "username",
"password": "password"
},
"local_prefs": {
"add_to_watch_folder": false,
"add_to_downloads_folder": false,
"local_watch_folder": "C:/watch",
"local_downloads_folder": "C:/downloads"
},
"ftp_prefs": {
"enable_ftp": false,
"add_to_watch_folder": false,
"add_to_downloads_folder": false,
"ftp_server": "server url",
"ftp_username": "username",
"ftp_password": "password",
"ftp_watch_folder": "/downloads/watch/transmission",
"ftp_downloads_folder": "/downloads"
}
}

+ 536
- 0
json_data/dictionary.json View File

@@ -0,0 +1,536 @@
{
"release_types": {
"Album": "Album",
"Single": "Single",
"EP": "Album",
"OST": "Album",
"싱글": "Single",
"EP(미니)": "Album",
"정규": "Album",
"컴필레이션": "Album",
"베스트": "Album",
"미니": "Album"
},
"genres": {
"R&B": "rnb",
"소울": "Soul",
"힙합": "hip.hop",
"랩": "Rap",
"영화": "Movie",
"로맨스": "Romance",
"드라마": "OST",
"TV 드라마": "OST",
"애니메이션": "anime",
"인디": "Indie",
"인디힙합": "Indie Hip-Hop",
"재즈 힙합": "Jazz-Hop",
"댄스 팝": "Dance",
"발라드": "Ballad",
"댄스": "Dance",
"포크 팝": "Folk",
"팝": "Pop",
"팝 락": "Pop.Rock",
"인디 락": "Indie.Rock",
"락": "Rock",
"메탈": "Metal",
"인디 팝": "Indie.Pop",
"일렉트로닉": "Electronic",
"일렉트로닉 팝": "Electro",
"인디일렉트로닉": "Indie.Electronic",
"신스 팝": "Synth-Pop",
"J-POP": "J-Pop",
"재즈": "Jazz",
"성인가요": "Trot",
"월드뮤직": "World Music",
"국악": "Traditional",
"종교": "Religious",
"CCM": "CCM",
"어린이": "Child",
"태교": "Taegyo",
"캐롤": "Christmas",
"트랩": "Trap",
"얼터너티브 팝": "Alternative.Pop",
"얼터너티브": "Alternative",
"뉴에이지": "New Age",
"켈틱": "Celtic",
"켈틱 퓨전": "Celtic.Fusion",
"퓨전": "Fusion",
"에스닉 퓨전": "Ethnic.Fusion",
"레게": "Reggae",
"댄스홀": "Dancehall",
"하우스": "House",
"트로트": "Trot",
"얼터너티브 락": "Alternative.Rock",
"덥": "Dub",
"싸이키델릭": "Psychedelic",
"인스트루멘탈 힙합": "Instrumental.Hip-Hop",
"인스트루멘탈": "Instrumental",
"클래식": "Classic",
"컨트리": "Country",
"종교음악": "Religious",
"전통음악": "Traditional",
"블루스": "Blues",
"라틴": "Latin",
"기타": "Other",
"기능성음악": "Functional",
"인디포크": "indie.folk",
"포크": "Folk",
"어쿠스틱": "Acoustic",
"Hip-Hop": "hip.hop"
},
"artist": {
"오아": "OA",
"이고": "Ego",
"ハルカトミユキ": null,
"琴音": null,
"下村陽子 × suis from ヨルシカ": null,
"川島ケイジ": null,
"裸体": null,
"空音": null,
"さかいゆう": null,
"美波": null,
"アルカラ": null,
"윤상": null,
"ブレッド & バター": null,
"Official髭男dism": null,
"優里": null,
"サニーデイ・サービス": null,
"ずっと真夜中でいいのに。": null,
"やなぎなぎ": null,
"米津玄師": null,
"梶浦由記": null,
"澁谷逆太郎": null,
"ポルカドットスティングレイ": null,
"김트와친구들": null,
"安斉かれん": null,
"坂口有望": null,
"空想委員会": null,
"ヨルシカ": null,
"向井太一": null,
"ペンギンラッシュ": null,
"黒子首": null,
"中島みゆき": null,
"ハリィさんとスイカくらぶ": null,
"堀込高樹": null,
"堀込泰行": null,
"スピラ・スピカ": null,
"17歳とベルリンの壁": null,
"天野月": null,
"ソールドシュガー": null,
"ナンカノユメ": null,
"ルルルルズ": null,
"東京事変": null,
"藍井エイル": null,
"阿部真央": null,
"赤いくらげ": null,
"週末CITY PLAY BOYZ": null,
"林 浩司": null,
"蒼山幸子": null,
"フラスコテーション": null,
"ゑんら": null,
"ハンブレッダーズ": null,
"鈴木このみ": null,
"みゆな": null,
"ビッケブランカ": null,
"めありー": null,
"キタニタツヤ": null,
"イロメガネ": null,
"ヤユヨ": null,
"ピロカルピン": null,
"ツユ": null,
"リリー楽綺団": null,
"山崎ハコ": null,
"いきものがかり": null,
"はるまきごはん": null,
"おくみずき": null,
"渣泥": null,
"竹渕慶": null,
"早見沙織": null,
"倖田來未": null,
"世武裕子": null,
"ラブリーサマーちゃん": null,
"SUPER☆GiRLS": null,
"österreich": null,
"フレデリック": null,
"ズーカラデル": null,
"神山羊": null,
"太田ひな": null,
"ヤバイTシャツ屋さん": null,
"當山みれい": null,
"大森靖子": null,
"大原櫻子": null,
"東京スカパラダイスオーケストラ": null,
"三月のパンタシア": null,
"雨のパレード": null,
"川崎鷹也": null,
"中島 美嘉": null,
"加藤ミリヤ": null,
"りぶ": null,
"雨ニマケテモ": null,
"三浦大知": null,
"コブクロ": null,
"ももいろクローバーZ": null,
"手嶌葵": null,
"Nao☆": null,
"尾崎裕哉": null,
"マーティ・フリードマン": null,
"幾田りら": null,
"山本彩": null,
"ビッケブランカ VS 岡崎体育": null,
"まるりとりゅうが": null,
"藤原さくら": null,
"藤井風": null,
"sicboy": "",
"LUCA & haruka nakamura": "arca",
"伊沢麻未": null,
"マカロニえんぴつ": null,
"チャラン・ポ・ランタン": null,
"鈴木瑛美子": null,
"神はサイコロを振らない": null,
"宇野実彩子": "AAA",
"ウルトラタワー": null,
"空白ごっこ": null,
"Cö shu Nie": null,
"くるり": null,
"流線形 & 一十三十一": null,
"清水翔太": null,
"あれくん": null,
"秋山黄色": null,
"웬디": "WENDY",
"瀧川ありさ": null,
"キリンジ": null,
"ユアネス": null,
"クレナズム": null,
"H△G": null,
"電音部": null,
"武藤彩未": null,
"中島美嘉": null,
"雫": null,
"坂本真綾": null,
"たかやん": null,
"布袋寅泰": null,
"アイラヴミー": null,
"ナナヲアカリ": null,
"福山雅治": null,
"Jacob&よみぃ": null,
"クミコ": null,
"リュックと添い寝ごはん": null,
"眉村ちあき": null,
"ちゃんみな & SKY-HI": null,
"関口シンゴ": null,
"角巻わため": null,
"Snail’s House": null,
"ロザリーナ": null,
"ニノミヤユイ": null,
"シド": null,
"森内寛樹": null,
"TK from 凛として時雨": null,
"スダンナユズユリー": null,
"ヤなことそっとミュート": null,
"足立佳奈": null,
"Rude-α": null,
"崎山蒼志": null,
"押尾コータロー×DEPAPEPE×崎山蒼志": null,
"清竜人": null,
"竹内アンナ": null,
"クレイユーキーズ with yui": "FLOWER FLOWER",
"fhána": null,
"カサリンチュ": null,
"西川貴教": null,
"瑛人": null,
"SMOKIN’theJAZZ": null,
"ピノキオピー": null,
"佐藤千亜妃": null,
"+α/あるふぁきゅん。": null,
"平井大": null,
"大橋トリオ": null,
"はるかりまあこ": null,
"海蔵亮太": null,
"関取花": null,
"八月二雪": null,
"ぼっちぼろまる & YACA IN DA HOUSE": null,
"ひじり": null,
"映秀。": null,
"吉澤嘉代子": null,
"山出愛子": null,
"SOIL &“PIMP”SESSIONS": null,
"R": "PKCZ",
"STUTS×SIKK-O×鈴木真海子": null,
"DJ松永": null,
"R-指定": null,
"輪入道": null,
"大森玲子": null,
"さユり": null,
"はてな": null,
"春ねむり": null,
"立山秋航": null,
"ナノ": null,
"雄之助": null,
"鈴木雅之": null,
"セイレーン": null,
"リーガルリリー": null,
"ドレスコーズ": null,
"校庭カメラガールドライ": null,
"門脇更紗": null,
"にしな": null,
"羽生まゐご": null,
"浜崎あゆみ": null,
"林明日香": null,
"木下百花": null,
"塩入冬湖": null,
"井筒昭雄": null,
"さだまさし": null,
"ねこね、こねこね。": null,
"ちゃんみな": null,
"冬にわかれて": null,
"알리샤": "Alisha",
"堤博明/照井順政/桶狭間ありさ": null,
"東京初期衝動": null,
"星野源": null,
"印象派": null,
"TOKYOてふてふ": null,
"ハク。": null,
"ヒトリエ": null,
"レルエ": null,
"モーモールルギャバン": null,
"Rin音": null,
"なきごと": null,
"おいしくるメロンパン": null,
"タイトル未定": null,
"アンテナガール": null,
"みんなのこどもちゃん": null,
"我儘ラキア": null,
"おーるどにゅーすぺーぱー": null,
"まこみなみん": null,
"ピューパ!!": null,
"みぃなとルーチ": null,
"名取さな": null,
"どーぷちゃん": null,
"藤井フミヤ": null,
"ハッカドール": null,
"変態紳士クラブ": null,
"広瀬こはる": null,
"富樫美鈴,水原朋也": null,
"橘直美": null,
"四宮小次郎": null,
"タクミ・アルディーニ": null,
"幸平創真": null,
"佐久間正英": null,
"ネクライトーキー": null,
"神田沙也加": null,
"PUNPEE×VaVa×OMSB": "SUMMIT",
"優里香": null,
"般若/ZORN/SHINGO★西成": null,
"トベタ・バジュン": null,
"ウォルピスカーター": null,
"レイラ": null,
"前島麻由": null,
"からっぽペペロンチーノ": null,
"椎名豪 featuring 中川奈美": null,
"清水あいり": null,
"吉田凜音": null,
"ππ来来 & ケンモチヒデフミ": null,
"yuigot & ぷにぷに電機": null,
"春乃こね子": null,
"TEMPLIME & 星宮とと": null,
"虹河ラキ": null,
"和楽器バンド": null,
"ACAね": null,
"夜とSAMPO": "",
"STUTS & 松たか子 with 3exes": null,
"伶": null,
"後藤まりこアコースティックviolence POP": null,
"ジェニーハイ": null,
"神様、僕は気づいてしまった": null,
"佐々木恵梨": null,
"ヴィヴィ(Vo.八木海莉)": null,
"須田景凪": null,
"一二三": null,
"メランコリーメランコリー": null,
"こゑだ": null,
"RUNG HYANG x claquepot x 向井太一": null,
"アポロノーム": null,
"クレイユーキーズ with DAZBEE": null,
"鬱P": null,
"安田レイ": null,
"揺らぎ": null,
"starscream & 栄免建設": null,
"小林私": null,
"くっつくパピー": null,
"久石 譲": null,
"どんぐりず": null,
"maeshima soshi & Rin音": null,
"WAЯROCK": null,
"サイプレス上野とロベルト吉野": null,
"かめりあ": null,
"畠中祐": null,
"坂本美雨 with CANTUS": null,
"re:plus × Ai Ninomiya": "R.A.",
"☆Taku Takahashi": null,
"クリス・ハート": null,
"神はサイコロを振らない × アユニ・D": null,
"夏目間風": null,
"大和田慧": null,
"カモメサノダブルクリックバンド": null,
"カネヨリマサル": null,
"荒田洸": null,
"景山将太": null,
"ゆに": null,
"土岐麻子": null,
"カワゴエリエ": null,
"ほのかりん": null,
"島爺": null,
"回路-kairo-": null,
"メ・ガーネ": null,
"스타트라인": "Startline",
"アイナ・ジ・エンド": null,
"フレンズ": null,
"鞘師里保": null,
"鈴木愛理": null,
"鈴木愛理 × Blue Vintage": null,
"仮谷せいら": null,
"from AAA": "SHINJIRO ATAE",
"周防パトラ": null,
"緑黄色社会": null,
"DE DE MOUSE, TANUKI, 一十三十一": null,
"あたらよ": null,
"神谷千尋": null,
"銀銀": null,
"みきまりあ": null,
"カンザキイオリ": null,
"おはようございます": null,
"ザ・モアイズユー": null,
"岸田教団 & THE明星ロケッツ": null,
"亜咲花": null,
"久石譲": null,
"青葉市子": null,
"青山テルマ": null,
"角野隼斗": null,
"オルターリードコード": null,
"ππ来来": null,
"毒島大蛇": null,
"Mi☆nA": null,
"パスピエ": null,
"ラックライフ": null,
"Maika Loubté": null,
"羊文学": null,
"鈴木真海子": null,
"埼玉最終兵器": null,
"星宮とと+TEMPLIME": null,
"ゴホウビ": null,
"15才と大森靖子": null,
"フミンニッキ": null,
"コンニチワトーキョー": null,
"ゆある": null,
"おはようツインテール": null,
"クレイジーケンバンド": null,
"ササノマリイ": null,
"日の当たる場所 & しの": null,
"絢香": null,
"Night Tempo/菊池桃子": null,
"月詠み": null,
"松尾太陽": null,
"今市隆二": null,
"コトフル": null,
"大国主": null,
"奈々生": null,
"悪羅王": null,
"∴ [yueni]": null,
"大沼パセリ": null,
"音楽的同位体 可不": "KAFU",
"神はサイコロを振らない & キタニタツヤ": null,
"ヰ世界情緒": null,
"春猿火": null,
"理芽": null,
"花譜": null,
"波羅ノ鬼": null,
"かいりきベア": null,
"平手友梨奈": null,
"高梨康治": null,
"保刈久明": null,
"沢田完": null,
"L'Arc~en~Ciel": null,
"星街すいせい": null,
"高槻かなこ": null,
"杏里": null,
"Leeu & こゆき": null,
"幸祜": null,
"りりあ。": null,
"DECO*27×堀江晶太": "kemu",
"溫蒂漫步": null,
"天輝おこめ": null,
"さよならポニーテール": null,
"ひかりのなかに": null,
"猫jealousy": null,
"パソコン音楽クラブ": null,
"家入レオ": null,
"アイマリン": null,
"オレンジスパイニクラブ": null,
"あいみょん": null,
"坂本美雨": null,
"岡崎体育": null,
"梶原岳人": null,
"三代目 J SOUL BROTHERS from EXILE TRIBE": null,
"高橋諒": null,
"麗奈": null,
"ポルノグラフィティ": null,
"ASIAN KUNG-FU GENERATION & 世武裕子": null,
"Cö Shu Nie": null,
"OKAMOTO’S": null,
"マハラージャン": null,
"ストレイテナー": null,
"スキマスイッチ": null,
"東京○X問題": null,
"ハルカミライ": null,
"明くる夜の羊": null,
"あるくとーーふ": null,
"岡部啓一・MONACA": null,
"光宗信吉": null,
"もさを。": null,
"ラバーキャロッツ": null,
"ウソツキ": null,
"長沼秀樹": null,
"眩暈SIREN": null,
"et-アンド-": null,
"フジファブリック": null,
"平沢進": null,
"ゲシュタルト乙女": null,
"エイプリルブルー": null,
"鹿乃": null,
"吉岡聖恵": null,
"鳳凰火凛": null,
"ケツメイシ": null,
"サイダーガール": null,
"cali≠gari": null,
"岸田教団&THE明星ロケッツ": null,
"ichigo from 岸田教団&THE明星ロケッツ": null,
"オレスカバンド": null,
"夏芽すやり & かききまなみ": null,
"サカナクション": null,
"聴色": null,
"みるきーうぇい": null,
"大塚愛": null,
"橋本絵莉子": null,
"CHANGMIN from 東方神起": null,
"ミクロミカ": null,
"和田アキ子": null,
"坂本龍一": null,
"天使うと": null,
"ブルー・ペパーズ": null,
"DÉ DÉ MOUSE & ぷにぷに電機": null,
"山崎あおい": null,
"milet×Aimer×幾田りら": null,
"数原龍友": null,
"稲葉曇": null,
"八木海莉": null,
"澤野弘之": null,
"澤田空海理": null,
"バウンダリー": null,
"がんばれ!隠れ汗プロジェクト": null,
"おとなりアイニー": null,
"高橋祐理・森重秀太 × mzsrz": "Zero PLANET",
"寺田創一": null,
"きゃりーぱみゅぱみゅ": null,
"macico & おかもとえみ": null
}
}

+ 8
- 0
requirements.txt View File

@@ -0,0 +1,8 @@
bs4
langdetect
mutagen
requests
torf
tqdm
html5lib
pymediainfo

+ 605
- 0
upload.py View File

@@ -0,0 +1,605 @@
# Standard library packages
from pickle import FALSE
import re
import os
import sys
import shutil
import string
import argparse
import html
from urllib.parse import urlparse
import json
import ftplib
# Third-party packages
import requests
from bs4 import BeautifulSoup
from torf import Torrent
from tqdm import tqdm
from langdetect import detect
from pymediainfo import MediaInfo
from pathlib import Path
# JPS-AU files
import jpspy
def asciiart ():
print("""
██╗██████╗ ███████╗ █████╗ ██╗ ██╗ ████████╗██╗ ██╗
██║██╔══██╗██╔════╝ ██╔══██╗██║ ██║ ╚══██╔══╝██║ ██║
██║██████╔╝███████╗█████╗███████║██║ ██║█████╗██║ ██║ ██║
██ ██║██╔═══╝ ╚════██║╚════╝██╔══██║██║ ██║╚════╝██║ ╚██╗ ██╔╝
╚█████╔╝██║ ███████║ ██║ ██║╚██████╔╝ ██║ ╚████╔╝
╚════╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝
""")
# Get arguments using argparse
def getargs():
"""
Get arguments using argparse
"""
parser = argparse.ArgumentParser()
parser.add_argument('-i', '--input', help='Initiate upload on input file', nargs='?', required=True)
parser.add_argument('-d', '--debug', help='Enable debug mode.', action='store_true')
parser.add_argument("-dry", "--dryrun", help="Dryrun will carry out all actions other than the actual upload to SM.", action="store_true")
parser.add_argument("-rt", "--releasetype", help='Set the release type. (PV, Music Performance, TV Music, TV Variety, TV-Drama)', nargs='?')
parser.add_argument("-a", "--artist", help='Set the artist. (Romaji\English). Split multiple with ","', nargs='?')
parser.add_argument("-oa", "--originalartist", help='Set the artist. (Original Language)', nargs='?')
parser.add_argument("-ti", "--title", help='Set the title. (Romaji\English)', nargs='?')
parser.add_argument("-oti", "--originaltitle", help='Set the title. (Original Language)', nargs='?')
parser.add_argument("-t", "--tags", help="Add additional tags to the upload. At least 2 tags are required", nargs='?')
parser.add_argument("-y", "--year", help='Set the torrent year (YYYYMMDD or YYYY).', nargs='?')
parser.add_argument("-fl", "--freeleech", help="Enables freeleech.", action="store_true")
parser.add_argument("-eti", "--editiontitle", help='Set the edition title', nargs='?')
parser.add_argument("-ey", "--editionyear", help='Set the torrent edition year (YYYYMMDD or YYYY).', nargs='?')
parser.add_argument("-ms", "--mediasource", help='Set the media source. (HDTV, Web)', nargs='?')
parser.add_argument("-im", "--imagepath", help='Set the torrent cover', nargs='?')
parser.add_argument("-tdes", "--torrentdescription", help='Add a torrent description', nargs='?')
parser.add_argument("-tgdes", "--torrentgroupdescription", help='Add a torrent group description. This is a required argument.', nargs='?', required=True)
parser.add_argument("-f", "--formattype", help='Set the media source. (MPEG, MPEG2, AVI, MKV, MP4, h264)', nargs='?')
return parser.parse_args()
# Acquire the authkey used for torrent files from upload.php
def getauthkey():
uploadpage = j.retrieveContent("https://jpopsuki.eu/upload.php")
soup = BeautifulSoup(uploadpage.text, 'html5lib')
rel2 = str(soup.select('#wrapper #content .thin'))
# Regex returns multiple matches, could be optimized.
authkey = re.findall("(?<=value=\")(.*)(?=\")", rel2)[0]
return authkey
def copytree(src, dst, symlinks=False, ignore=None):
for item in os.listdir(src):
s = os.path.join(src, item)
d = os.path.join(dst, item)
if os.path.isdir(s):
shutil.copytree(s, d, symlinks, ignore)
else:
shutil.copy2(s, d)
# Creates torrent file using torf module.
def createtorrent(authkey, filepath, releasedata):
t = Torrent(path=filepath,
trackers=[authkey]) # Torf requires we store authkeys in a list object. This makes it easier to add multiple announce urls.
# Set torrent to private as standard practice for private trackers
t.private = True
t.generate()
## Format releasedata to bring a suitable torrent name.
# The reason we don't just use the directory name is because of an error in POSTING.
# POSTS do not seem to POST hangul/jp characters alongside files.
filename = f"{releasedata['title']} [{releasedata['media']}-{releasedata['format']}].torrent"
filename = filename.replace("/","/").replace(":",":").replace("?","?").replace("\"","")
try:
t.write(filename)
print("_" * 100)
print("Torrent creation:\n")
print(f"{filename} has been created.")
except:
print("_" * 100)
print("Torrent creation:\n")
os.remove(filename)
print(f"{filename} already exists, existing torrent will be replaced.")
t.write(filename)
print(f"{filename} has been created.")
return filename
def add_to_hangul_dict(hangul , english , category):
hangul = str(hangul)
english = str(english)
categories = ['version','general','artist','genres', 'label', 'distr']
file = f"json_data/dictionary.json"
json_file = open(file, 'r', encoding='utf-8', errors='ignore')
dictionary = json.load(json_file)
json_file.close()
new = dict()
for cats in dictionary:
#== Create the categories in the new temp file
new[cats] = dict()
for key,value in dictionary[cats].items():
#== List all the old items into the new dict
new[cats][key] = value
if hangul in new[category].keys():
if new[category].get(hangul) is None:
if english != 'None':
new[category][hangul] = english
else:
#== Only update if English word has been supplied ==#
if english != 'None':
new[category][hangul] = english
else:
if english == 'None':
new[category][hangul] = None
else:
new[category][hangul] = english
json_write = open(file, 'w+', encoding='utf-8')
json_write.write(json.dumps(new, indent=4, ensure_ascii=False))
json_write.close()
def translate(string, category, result=None, output=None):
file = "json_data/dictionary.json"
with open(file, encoding='utf-8', errors='ignore') as f:
dictionary = json.load(f, strict=False)
category = str(category)
string = str(string)
search = dictionary[category]
string = string.strip()
if string == 'Various Artists':
output = ['Various Artists',None]
else:
#== NO NEED TO SEARCH - STRING HAS HANGUL+ENGLISH or HANGUL+HANGUL ==#
if re.search("\((?P<inside>.*)\)", string):
#== Complete translation, add to dictionary with both values ==#
#== Contains parentheses, need to split
parenthesis = string.split("(")
pre_parenthesis = parenthesis[0].strip()
in_parenthesis = parenthesis[1].replace(")","").strip()
#== Check the order of the parentheses ==#
if re.search("[^\u0000-\u007F]+",pre_parenthesis) and re.search("[^\u0000-\u007F]+",in_parenthesis):
#== Both hangul
first = 'kr'
second = 'kr'
else:
if re.search("[^\u0000-\u007F]+",pre_parenthesis):
first = 'kr'
second = 'eng'
else:
first = 'eng'
second = 'kr'
if first == 'kr' and second == 'eng':
#== Hangul first ==#
hangul = pre_parenthesis
english = in_parenthesis
add_to_hangul_dict(hangul,english,category)
elif first == 'eng' and second == 'kr':
#== English first ==#
hangul = in_parenthesis
english = pre_parenthesis
add_to_hangul_dict(hangul,english,category)
elif first == 'kr' and second == 'kr':
#== Both Hangul ==#
hangul = pre_parenthesis
english = None
add_to_hangul_dict(pre_parenthesis,None,category)
add_to_hangul_dict(hangul,None,category)
else:
#== Both English
hangul = None
english = pre_parenthesis
output = [hangul,english]
#== No parentheses - HANGUL
else:
#== If the input string is a full Hangul word - check dictionary and then add if necessary)
if re.search("[^\u0000-\u007F]+", string):
if string in search.keys():
#== yes
if search.get(string) is None:
#== If the keyword does not have a translation, add it to the dictionary ==#
output = [string,None]
else:
#== Translation already exists, output the result in a list ==#
output = [string,search.get(string)]
else:
output = [string,None]
add_to_hangul_dict(string, None, category)
#== Full English name -- leave it
else:
for key,value in search.items():
if key == string:
output = [value,string]
break
else:
output = [string,string]
return output
def gatherdata():
"""
Retrieve data about the upload. Ask for user input if necessary.
:return: releasedata: dict
"""
releasedata = {"submit": "true"}
releasedata["album_desc"] = torrentgroupdescription
if torrentdescription:
releasedata['release_desc'] = torrentdescription
list_of_types = ["PV", "Music Performance", "TV Music", "TV Variety", "TV-Drama"]
if releasetype in list_of_types:
if releasetype == "PV":
releasedata["type"] = "PV"
elif releasetype == "TV Music":
releasedata["type"] = "TV-Music"
elif releasetype == "TV Variety":
releasedata["type"] = "TV-Variety"
elif releasetype == "TV-Drama":
releasedata["type"] = "TV-Drama"
else:
while(True):
input_lang = input("\n" + "_" * 100 + "\nEnter a number to choose the upload type. \n1=PV\n2=TV-Music\n3=TV-Variety\n4=TV-Drama\n")
if input_lang == "1":
releasedata["type"] = "PV"
break
elif input_lang == "2":
releasedata["type"] = "TV-Music"
break
elif input_lang == "3":
releasedata["type"] = "TV-Variety"
break
elif input_lang == "4":
releasedata["type"] = "TV-Drama"
break
print("Invalid choice.")
if artist:
releasedata['artist'] = artist
else:
print(artist)
input_english_artist = input("\n" + "_" * 100 + "\nEnter the romaji/english ARTIST name.\n")
releasedata['artist'] = input_english_artist
if originalartist:
releasedata['artistjp'] = originalartist
else:
input_artist = input("\n" + "_" * 100 + "\nEnter the original ARTIST name. Press enter to skip if this torrent has the artist name already english or already has a artist page.\n")
releasedata['artistjp'] = input_artist
if title:
releasedata['title'] = title
else:
input_english_title = input("\n" + "_" * 100 + "\nEnter the romaji/english TITLE:\n")
releasedata['title'] = input_english_title
if originaltitle:
releasedata['titlejp'] = originaltitle
else:
input_title = input("\n" + "_" * 100 + "\nEnter the original TITLE. Press enter to skip.\n")
releasedata['titlejp'] = input_title
if year:
releasedata["releasedate"] = year
else:
input_year = input("\n" + "_" * 100 + "\nEnter the year as YYYYMMDD or YYYY.\n")
releasedata["releasedate"] = input_year
if editiontitle:
releasedata['remaster_title'] = editiontitle
else:
input_editiontitle = input("\n" + "_" * 100 + "\nEnter the edition TITLE. Press enter to skip.\n")
if input_editiontitle != "":
if editionyear:
releasedata["remaster_year"] = editionyear
else:
input_editionyear = input("\n" + "_" * 100 + "\nEnter the edition year as YYYY.\n")
releasedata["remaster_year"] = input_editionyear
releasedata['remaster_title'] = input_editiontitle
if formattype:
releasedata['format'] = formattype
else:
while(True):
input_format = input("\n" + "_" * 100 + "\nEnter a number to choose the format. \n1=MPEG\n2=MPEG2\n3=AVI\n4=MKV\n5=MP4\n6=h264\n")
if input_format == "1":
releasedata["format"] = "MPEG"
break
elif input_format == "2":
releasedata["format"] = "MPEG2"
break
elif input_format == "3":
releasedata["format"] = "AVI"
break
elif input_format == "4":
releasedata["format"] = "MKV"
break
elif input_format == "5":
releasedata["format"] = "MP4"
break
elif input_format == "6":
releasedata["format"] = "h264"
break
print("Invalid choice.")
if mediasource:
releasedata['media'] = mediasource
else:
while(True):
input_media = input("\n" + "_" * 100 + "\nEnter a number to choose the media source. \n1=HDTV\n2=Web\n")
if input_media == "1":
releasedata["media"] = "HDTV"
break
elif input_media == "2":
releasedata["media"] = "Web"
break
print("Invalid choice.")
if tags:
releasedata["tags"] = tags
else:
while(True):
input_tags = input("\n" + "_" * 100 + "\nEnter the tags. Separate multiple with \",\". Minimum 1 tag required.\n")
if len(input_tags.split(",")) != 0:
releasedata["tags"] = input_tags
break
else:
print("Please enter at least one tag.")
return releasedata
# MediaInfo.parse doesnt work properly right now. it has duplicate lines
def add_mediainfo_to_releasedata(filename, releasedata):
"""
Retrieve mediainfo and append it to the releasedata dictionary.
:return: releasedata: dict
"""
mediainfosall = ""
mediainfosall += str(MediaInfo.parse(filename, output="text"))
replacement = str(Path(filename).parent)
mediainfosall = mediainfosall.replace(replacement, '')
#releasedata["release_desc"] += "\n\n" + mediainfosall
print(mediainfosall)
return releasedata
# Simple function to split a string up into characters
def split(word):
return [char for char in word]
def detectlanguage(string):
## Language Detect
# This is a required check as we don't want to enter non-english/romaji characters into the title field.
characters = split(string)
language_list = []
for c in characters:
try:
language = detect(c)
language_list.append(language)
except:
langauge = "error"
if 'ko' or 'ja' in language_list:
en = False
else:
en = True
return en
def uploadtorrent(torrent, cover, releasedata):
# POST url.
uploadurl = "https://jpopsuki.eu/upload.php"
# Dataset containing all of the information obtained from our FLAC files.
data = releasedata
if debug:
print('_' * 100)
print('Release Data:\n')
print(releasedata)
try:
postDataFiles = {
'file_input': open(torrent, 'rb'),
'userfile': open(cover, 'rb')
}
except FileNotFoundError:
print("_" * 100)
print('File not found!\nPlease confirm file locations and names. Cover image or .torrent file could not be found')
sys.exit()
# If dryrun argument has not ben passed we will POST the results to JPopSuki.
if dryrun != True:
JPSres = j.retrieveContent(uploadurl, "post", data, postDataFiles)
print('\nUpload POSTED')
## TODO Filter through JPSres.text and create error handling based on responses
#print(JPSres.text)
# Function for transferring the contents of the torrent as well as the torrent.
def ftp_transfer(fileSource, fileDestination, directory, folder_name, watch_folder):
# Create session
session = ftplib.FTP(cfg['ftp_prefs']['ftp_server'],cfg['ftp_prefs']['ftp_username'],cfg['ftp_prefs']['ftp_password'])
# Set session encoding to utf-8 so we can properly handle hangul/other special characters
session.encoding='utf-8'
# Successful FTP Login Print
print("_" * 100)
print("FTP Login Successful")
print(f"Server Name: {cfg['ftp_prefs']['ftp_server']} : Username: {cfg['ftp_prefs']['ftp_username']}\n")
if cfg['ftp_prefs']['add_to_downloads_folder']:
# Create folder based on the directory name of the folder within the torrent.
try:
session.mkd(f"{fileDestination}/{folder_name}")
print(f'Created directory {fileDestination}/{folder_name}')
except ftplib.error_perm:
pass
# Notify user we are beginning the transfer.
print(f"Beginning transfer...")
# Set current folder to the users preferred destination
session.cwd(f"{fileDestination}/{folder_name}")
# Transfer each file in the chosen directory
for file in os.listdir(directory):
with open(f"{directory}/{file}",'rb') as f:
filesize = os.path.getsize(f"{directory}/{file}")
## Transfer file
# tqdm used for better user feedback.
with tqdm(unit = 'blocks', unit_scale = True, leave = False, miniters = 1, desc = f'Uploading [{file}]', total = filesize) as tqdm_instance:
session.storbinary('STOR ' + file, f, 2048, callback = lambda sent: tqdm_instance.update(len(sent)))
print(f"{file} | Complete!")
f.close()
if cfg['ftp_prefs']['add_to_watch_folder']:
with open(fileSource,'rb') as t:
# Set current folder to watch directory
session.cwd(watch_folder)
## Transfer file
# We avoid tqdm here due to the filesize of torrent files.
# Most connections will upload these within 1-3s, resulting in near useless progress bars.
session.storbinary(f"STOR {torrentfile}", t)
print(f"{torrentfile} | Sent to watch folder!")
t.close()
# Quit session when complete.
session.quit()
def localfileorganization(torrent, directory, watch_folder, downloads_folder):
# Move torrent directory to downloads_folder
if cfg['local_prefs']['add_to_downloads_folder']:
try:
os.mkdir(os.path.join(downloads_folder, os.path.basename(directory)))
except FileExistsError:
pass
copytree(directory, os.path.join(downloads_folder, os.path.basename(directory)))
shutil.rmtree(directory)
if cfg['local_prefs']['add_to_watch_folder']:
os.rename(torrent, f"{watch_folder}/{torrent}")
if __name__ == "__main__":
asciiart()
args = getargs()
# TODO consider calling args[] directly, we will then not need this line
dryrun = debug = tags = artist = title = formattype = imagepath = freeleech = None
originalartist = originaltitle = torrentdescription = torrentgroupdescription = editiontitle = editionyear = year = mediasource = releasetype = None
inputfile = args.input
torrentgroupdescription = args.torrentgroupdescription
torrentdescription = args.torrentdescription
if args.dryrun:
dryrun = True
if args.debug:
debug = True
if args.freeleech:
freeleech = True
if args.releasetype:
releasetype = args.releasetype
if args.title:
title = args.title
if args.artist:
artist = args.artist
if args.originalartist:
originalartist = args.originalartist
if args.originaltitle:
originaltitle = args.originaltitle
if args.editiontitle:
editiontitle = args.editiontitle
if args.year:
year = args.year
if args.editionyear:
editionyear = args.editionyear
if args.mediasource:
mediasource = args.mediasource
if args.formattype:
format_type = args.formattype
if args.tags:
tags = args.tags
if args.imagepath:
imagepath = args.imagepath
# Load login credentials from JSON and use them to create a login session.
with open(f'json_data/config.json') as f:
cfg = json.load(f)
loginData = {'username': cfg['credentials']['username'], 'password': cfg['credentials']['password']}
loginUrl = "https://jpopsuki.eu/login.php"
loginTestUrl = "https://jpopsuki.eu"
successStr = "Latest 5 Torrents"
# j is an object which can be used to make requests with respect to the loginsession
j = jpspy.MyLoginSession(loginUrl, loginData, loginTestUrl, successStr, debug=args.debug)
# Acquire authkey
authkey = getauthkey()
# Gather data of the file
releasedata = gatherdata()
# releasedata_and_mediainfo = add_mediainfo_to_releasedata(inputfile, releasedata)
# Folder_name equals the last folder in the path, this is used to rename .torrent files to something relevant.
# folder_name = os.path.basename(os.path.normpath(directory))
# Identifying cover.jpg path
# cover_path = directory + "/" + cfg['local_prefs']['cover_name']
# Create torrent file.
torrentfile = createtorrent(authkey, inputfile, releasedata)
# Upload torrent to JPopSuki
uploadtorrent(torrentfile, imagepath, releasedata)
# Setting variable for watch/download folders
ftp_watch_folder = cfg['ftp_prefs']['ftp_watch_folder']
ftp_downloads_folder = cfg['ftp_prefs']['ftp_downloads_folder']
local_watch_folder = cfg['local_prefs']['local_watch_folder']
local_downloads_folder = cfg['local_prefs']['local_downloads_folder']
# if cfg['ftp_prefs']['enable_ftp']:
# ftp_transfer(fileSource=torrentfile, fileDestination=ftp_downloads_folder, directory=directory, folder_name=folder_name, watch_folder=ftp_watch_folder)
# if cfg['local_prefs']['add_to_watch_folder'] or cfg['local_prefs']['add_to_downloads_folder']:
# localfileorganization(torrent=torrentfile, directory=directory, watch_folder=local_watch_folder, downloads_folder=local_downloads_folder)
if not dryrun:
if cfg['local_prefs']['add_to_watch_folder']:
os.rename(torrentfile, f"{local_watch_folder}/{torrentfile}")

Loading…
Cancel
Save