├── .gitignore ├── eg.gif ├── requirements.txt ├── config.json ├── setup.sh ├── src ├── test.py ├── spinner.py ├── piratebay.py └── app.py └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | env 2 | src/__pycache__ 3 | -------------------------------------------------------------------------------- /eg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwivedi-ritik/t-stream/HEAD/eg.gif -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.11.1 2 | bs4==0.0.1 3 | requests==2.27.1 4 | rich==12.2.0 5 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "player": "mpv", 4 | "client": "webtorrent" 5 | } 6 | } -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Install peerflix 4 | 5 | sudo npm -g i peerflix 6 | sudo npm install webtorrent-cli -g 7 | pip install -r requirements.txt 8 | 9 | sl=$(echo `(which $SHELL)` | awk -F "/" '{print $3}') 10 | 11 | echo "alias t-stream='python $(pwd)/src/app.py'" >> ~/.${sl}rc 12 | -------------------------------------------------------------------------------- /src/test.py: -------------------------------------------------------------------------------- 1 | def parse_config(): 2 | app_dir = "/".join(os.path.realpath(__file__).split("/")[:-2]) 3 | config_path = os.path.join(app_dir , "config.json") 4 | 5 | default_value = ("webtorrent" , "mpv") 6 | players = ["mpv" , "vlc"] 7 | clients = ["webtorrent" , "peerflix"] 8 | 9 | if not Path(config_path).is_file(): 10 | return default_value 11 | 12 | with open(config_path) as f: 13 | config = json.loads(f.read()) 14 | 15 | if config["config"]["player"] and config["config"]["client"]: 16 | if config["config"]["player"] in players and config["config"]["client"] in clients: 17 | return ( config["config"]["player"] , config["config"]["client"] ) 18 | 19 | return default_value 20 | 21 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## t-stream 2 | Stream your favorite torrent in your terminal 3 | 4 | ![video](./eg.gif) 5 | ### How to use ? 6 | - Install all dependecy and library by running`setup.sh`. 7 | - restart your terminal 8 | - run `t-stream` 9 | - you can pass torrent name in command line argument in `t-stream` 10 | 11 | ### Configure 12 | - You can set your player and torrent-client in `config.json` file. 13 | - Only support mpv or vlc and webtorrent or peerflix. 14 | - default player is `mpv` and client in `webtorrent`. 15 | 16 | ### Dependencies 17 | - [webtorrent-cli](https://github.com/webtorrent/webtorrent-cli) 18 | - [peerflix](https://github.com/mafintosh/peerflix) 19 | 20 | 21 | ### Note 22 | `mpv` is default player for linux based OS . 23 | If you are on windows you have to manually setup and install all requirement files. -------------------------------------------------------------------------------- /src/spinner.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | 3 | import sys 4 | import time 5 | import threading 6 | import random 7 | 8 | spinners = [ ["⣾","⣽","⣻","⢿","⡿","⣟","⣯","⣷"] , ["✶","✸","✹", "✺", "✹", "✷"] , ["⠄","⠆","⠇","⠋","⠙", "⠸","⠰","⠠","⠰","⠸","⠙","⠋","⠇","⠆"]] 9 | 10 | fav = random.choice(spinners) 11 | 12 | def remove_cursor(): 13 | sys.stdout.write("\033[?25l") 14 | 15 | def add_cursor(): 16 | sys.stdout.write("\033[?25h") 17 | 18 | class Spinner: 19 | busy = False 20 | delay = 0.2 21 | 22 | @staticmethod 23 | def spinning_cursor(): 24 | while 1: 25 | for cursor in fav: yield cursor 26 | 27 | def __init__(self, delay=None): 28 | self.spinner_generator = self.spinning_cursor() 29 | if delay and float(delay): self.delay = delay 30 | 31 | def spinner_task(self): 32 | remove_cursor() 33 | sys.stdout.write("\u001b[35;1m") 34 | while self.busy: 35 | sys.stdout.write(next(self.spinner_generator)) 36 | sys.stdout.flush() 37 | time.sleep(self.delay) 38 | sys.stdout.write('\b') 39 | sys.stdout.flush() 40 | sys.stdout.write("\u001b[0m") 41 | add_cursor() 42 | 43 | def __enter__(self): 44 | self.busy = True 45 | threading.Thread(target=self.spinner_task).start() 46 | 47 | def __exit__(self, exception, value, tb): 48 | self.busy = False 49 | time.sleep(self.delay) 50 | if exception is not None: 51 | return False 52 | 53 | -------------------------------------------------------------------------------- /src/piratebay.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | from pprint import pprint 3 | import unicodedata 4 | 5 | import requests 6 | from bs4 import BeautifulSoup 7 | 8 | def get_json(soup): 9 | table = soup.table 10 | tables = table.find_all("td") 11 | 12 | json_obj = { 13 | "movie_info":[] 14 | } 15 | seeders = [] 16 | movie_sizes = [] 17 | 18 | for el in table.find_all("td"): 19 | if el.font: 20 | size = el.font.text.split(",")[1][6:] 21 | movie_sizes.append(size) 22 | 23 | if el.div and el.div.a: 24 | json_obj["movie_info"].append({ 25 | "title": el.div.text.strip("\n"), 26 | "magnet_url": el.find_all("a")[1]["href"] 27 | }) 28 | if el.get("align"): 29 | seeders.append(el.text) 30 | 31 | k = 0 32 | for i in range(0 , len(seeders) , 2): 33 | json_obj["movie_info"][k]["seeders"] = seeders[i] 34 | json_obj["movie_info"][k]["leeches"] = seeders[i+1] 35 | k += 1 36 | 37 | for i , _ in enumerate(json_obj["movie_info"]): 38 | json_obj["movie_info"][i]["size"] = unicodedata.normalize("NFKD" , movie_sizes[i]) 39 | 40 | return json_obj 41 | 42 | def pirate(query = None): 43 | if not query: 44 | url = "https://tpb.party/top/200" 45 | else: 46 | url = f"https://tpb.party/search/{query}" 47 | res = requests.get(url) 48 | if res.status_code != 200: 49 | raise ValueError("Ops didn't get valid response") 50 | content = res.content 51 | soup = BeautifulSoup(content , "html.parser") 52 | obj = get_json(soup) 53 | return obj 54 | -------------------------------------------------------------------------------- /src/app.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | import sys 3 | import subprocess 4 | import json 5 | import os 6 | from pathlib import Path 7 | 8 | from rich.console import Console 9 | from rich.table import Table 10 | from rich.prompt import Prompt 11 | from piratebay import * 12 | from spinner import Spinner , add_cursor 13 | 14 | 15 | def write_table(movie_list): 16 | table = Table(show_header=True, header_style="bold magenta") 17 | table.add_column("ID", width=12) 18 | table.add_column("Title") 19 | table.add_column("Size", justify="right") 20 | table.add_column("Seeders", justify="right") 21 | table.add_column("Leeches", justify="right") 22 | 23 | for i , obj in enumerate( movie_list ): 24 | table.add_row(str(i+1) , obj["title"] , str(obj["size"]),str(obj["seeders"]) , str(obj["leeches"])) 25 | 26 | console.print(table) 27 | return 28 | 29 | def greet_bye(): 30 | print("\nsee ya 👋") 31 | 32 | 33 | def parse_config(): 34 | app_dir = "/".join(os.path.realpath(__file__).split("/")[:-2]) 35 | config_path = os.path.join(app_dir , "config.json") 36 | 37 | default_value = ("webtorrent" , "mpv") 38 | players = ["mpv" , "vlc"] 39 | clients = ["webtorrent" , "peerflix"] 40 | 41 | if not Path(config_path).is_file(): 42 | return default_value 43 | 44 | with open(config_path) as f: 45 | config = json.loads(f.read()) 46 | 47 | if config["config"]["player"] and config["config"]["client"]: 48 | if config["config"]["player"] in players and config["config"]["client"] in clients: 49 | return ( config["config"]["client"] , config["config"]["player"] ) 50 | 51 | return default_value 52 | 53 | def stream(mag_url): 54 | client , player = parse_config() 55 | subprocess.run([client , mag_url , f"--{player}" ]) 56 | 57 | 58 | console = Console() 59 | 60 | try: 61 | if len(sys.argv) > 1: 62 | query = "".join(sys.argv[1:]) 63 | else: 64 | query = Prompt.ask("What you want to watch today ?") 65 | 66 | print(" Finding torrents" , end="\r") 67 | 68 | with Spinner(): 69 | if query == "1": 70 | movie_list = pirate()["movie_info"] 71 | else: 72 | movie_list = pirate(query=query)["movie_info"] 73 | 74 | if len(movie_list) == 0: 75 | greet_bye() 76 | exit(1) 77 | 78 | write_table(movie_list) 79 | 80 | movie_ind = Prompt.ask("Select your fav" , default="1") 81 | if int(movie_ind) >= len(movie_list): 82 | mag_url = movie_list[-1]["magnet_url"] 83 | else: 84 | mag_url = movie_list[int(movie_ind)-1]["magnet_url"] 85 | 86 | except KeyboardInterrupt: 87 | add_cursor() 88 | greet_bye() 89 | exit(1) 90 | 91 | print("Enjoy! Less seeds may take more time\nStreaming will start after 1% of downloading") 92 | stream(mag_url) 93 | 94 | 95 | 96 | 97 | 98 | --------------------------------------------------------------------------------