rpirate 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ recursive-include src/pirate_three/templates *
2
+ recursive-include src/pirate_three/static *
3
+ include src/pirate_three/default_config.yaml
rpirate-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,56 @@
1
+ Metadata-Version: 2.4
2
+ Name: rpirate
3
+ Version: 0.1.0
4
+ Summary: A full stack torrent streaming app to get you free movies.
5
+ Author: Miles Hilliard
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.7
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: Flask
12
+ Requires-Dist: python-vlc
13
+ Requires-Dist: PyYAML
14
+ Requires-Dist: requests
15
+ Requires-Dist: tpblite
16
+
17
+ # PiRate v3.0
18
+
19
+ Stream your favorite movies and TV shows for free.
20
+
21
+ PiRate is a modern, mobile-friendly web interface that wraps The Pirate Bay (TPB) and the qBittorrent API to provide a seamless, Netflix-like streaming experience from your couch.
22
+
23
+ > **Disclaimer:** This software is an interface for third-party APIs and torrent sites. The creator of this software is not responsible for any content downloaded, hosted, or distributed. Please use a VPN and use responsibly in accordance with your local laws.
24
+
25
+ ## Features
26
+
27
+ - **Cinematic UI:** Beautiful, responsive dark-mode interface optimized for mobile and desktop.
28
+ - **Dynamic Content:** Browse trending and genre-specific movies fetched directly via the YTS API.
29
+ - **Virtual Remote:** Control playback directly from your smartphone with a sleek web remote.
30
+ - **Live Downloads:** Monitor and manage your active torrents with real-time progress.
31
+
32
+ ## Quick Start
33
+
34
+ ### 1. Prerequisites
35
+ Ensure you have the following installed:
36
+ - [Python 3](https://www.python.org/downloads/)
37
+ - [VLC Media Player](https://www.videolan.org/vlc/)
38
+ - [qBittorrent](https://www.qbittorrent.org/)
39
+ - A reliable VPN (e.g., [Proton VPN](https://protonvpn.com))
40
+
41
+ ### 2. Installation
42
+ ```sh
43
+ git clone https://github.com/syntaxerror019/pirate-three.git
44
+ cd pirate-three
45
+ pip install -r requirements.txt
46
+ ```
47
+
48
+ ### 3. Usage
49
+ Make sure qBittorrent is running in the background, then start the PiRate server:
50
+ ```sh
51
+ python3 main.py
52
+ ```
53
+ Open your browser and navigate to `http://localhost:3000`.
54
+
55
+ ## Contributing
56
+ Issues and Pull Requests are always welcome!
@@ -0,0 +1,40 @@
1
+ # PiRate v3.0
2
+
3
+ Stream your favorite movies and TV shows for free.
4
+
5
+ PiRate is a modern, mobile-friendly web interface that wraps The Pirate Bay (TPB) and the qBittorrent API to provide a seamless, Netflix-like streaming experience from your couch.
6
+
7
+ > **Disclaimer:** This software is an interface for third-party APIs and torrent sites. The creator of this software is not responsible for any content downloaded, hosted, or distributed. Please use a VPN and use responsibly in accordance with your local laws.
8
+
9
+ ## Features
10
+
11
+ - **Cinematic UI:** Beautiful, responsive dark-mode interface optimized for mobile and desktop.
12
+ - **Dynamic Content:** Browse trending and genre-specific movies fetched directly via the YTS API.
13
+ - **Virtual Remote:** Control playback directly from your smartphone with a sleek web remote.
14
+ - **Live Downloads:** Monitor and manage your active torrents with real-time progress.
15
+
16
+ ## Quick Start
17
+
18
+ ### 1. Prerequisites
19
+ Ensure you have the following installed:
20
+ - [Python 3](https://www.python.org/downloads/)
21
+ - [VLC Media Player](https://www.videolan.org/vlc/)
22
+ - [qBittorrent](https://www.qbittorrent.org/)
23
+ - A reliable VPN (e.g., [Proton VPN](https://protonvpn.com))
24
+
25
+ ### 2. Installation
26
+ ```sh
27
+ git clone https://github.com/syntaxerror019/pirate-three.git
28
+ cd pirate-three
29
+ pip install -r requirements.txt
30
+ ```
31
+
32
+ ### 3. Usage
33
+ Make sure qBittorrent is running in the background, then start the PiRate server:
34
+ ```sh
35
+ python3 main.py
36
+ ```
37
+ Open your browser and navigate to `http://localhost:3000`.
38
+
39
+ ## Contributing
40
+ Issues and Pull Requests are always welcome!
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "rpirate"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name="Miles Hilliard" },
10
+ ]
11
+ description = "A full stack torrent streaming app to get you free movies."
12
+ readme = "README.md"
13
+ requires-python = ">=3.7"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+ dependencies = [
20
+ "Flask",
21
+ "python-vlc",
22
+ "PyYAML",
23
+ "requests",
24
+ "tpblite"
25
+ ]
26
+
27
+ [project.scripts]
28
+ pirate = "pirate_three:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ from .main import main
@@ -0,0 +1,33 @@
1
+ import yaml
2
+ import os
3
+ import shutil
4
+
5
+ class Config():
6
+ def __init__(self, config_file="config.yaml"):
7
+ self.config_file = config_file
8
+ self.ensure_config()
9
+ self.load_config()
10
+
11
+ def ensure_config(self):
12
+ """Ensure the config file exists in the current working directory, otherwise copy default."""
13
+ if not os.path.exists(self.config_file):
14
+ print(f"[{self.config_file}] not found in current directory. Creating default configuration.")
15
+
16
+ # Find default_config.yaml in the package directory
17
+ pkg_dir = os.path.dirname(os.path.abspath(__file__))
18
+ default_config_path = os.path.join(pkg_dir, "default_config.yaml")
19
+
20
+ if os.path.exists(default_config_path):
21
+ shutil.copy(default_config_path, self.config_file)
22
+ else:
23
+ print("Error: Package default_config.yaml is missing!")
24
+
25
+ def load_config(self):
26
+ if os.path.exists(self.config_file):
27
+ with open(self.config_file, 'r') as file:
28
+ self.config = yaml.safe_load(file) or {}
29
+ else:
30
+ self.config = {}
31
+
32
+ def get(self, key, default=None):
33
+ return self.config.get(key, default)
@@ -0,0 +1,24 @@
1
+ ############### www.mileshilliard.com ###################
2
+ #
3
+ # User Configuration File.
4
+ #
5
+ # Adjust settings per your taste and preference!
6
+ #
7
+ # The default settings should work fine,
8
+ # so there is no immediate need to touch this.
9
+ #
10
+ #########################################################
11
+
12
+ download_folder: /home/miles/Downloads/pirate-downloads
13
+ create_folder: true
14
+
15
+ # Network
16
+
17
+ tbp_url: https://thepiratebay7.com/
18
+ host: 0.0.0.0
19
+ port: 3000
20
+
21
+ # QBitTorrent
22
+
23
+ qb_url: http://localhost:8080
24
+ use_old_api: false # Leave off unless you have an old version of qBittorrent
@@ -0,0 +1,39 @@
1
+ import logging
2
+ import sys
3
+
4
+ # ANSI escape codes
5
+ LOG_COLORS = {
6
+ "DEBUG": "\033[36m", # Cyan
7
+ "INFO": "\033[32m", # Green
8
+ "WARNING": "\033[33m", # Yellow
9
+ "ERROR": "\033[31m", # Red
10
+ "CRITICAL": "\033[35m", # Magenta
11
+ "RESET": "\033[0m" # Reset to default
12
+ }
13
+
14
+ class ColoredFormatter(logging.Formatter):
15
+ def format(self, record):
16
+ log_color = LOG_COLORS.get(record.levelname, LOG_COLORS["RESET"])
17
+ record.msg = f"{log_color}{record.msg}{LOG_COLORS['RESET']}"
18
+ return super().format(record)
19
+
20
+ logging.basicConfig(
21
+ level=logging.DEBUG,
22
+ format="%(asctime)s - %(levelname)s - %(message)s",
23
+ handlers=[
24
+ logging.FileHandler("app.log"), # Log to a file (no colors)
25
+ logging.StreamHandler(sys.stdout) # log to console
26
+ ]
27
+ )
28
+
29
+ # colored formatting to console logs only
30
+ console_handler = logging.getLogger().handlers[1] # Second handler (StreamHandler)
31
+ console_handler.setFormatter(ColoredFormatter("%(asctime)s - %(levelname)s - %(message)s"))
32
+
33
+ if __name__ == "__main__":
34
+ # Test the logger
35
+ logging.debug("This is a debug message")
36
+ logging.info("This is an info message")
37
+ logging.warning("This is a warning")
38
+ logging.error("This is an error")
39
+ logging.critical("This is a critical error")
@@ -0,0 +1,216 @@
1
+ from flask import Flask, request, jsonify, render_template
2
+ from tpblite import TPB
3
+ import subprocess
4
+ import socket
5
+ import urllib.request
6
+ import json
7
+ import os
8
+ import sys
9
+
10
+ from .player import Player
11
+ from .torrent import tr
12
+ from .config import Config
13
+
14
+ # Init Flask with explicit template/static folder paths so it works when installed via pip
15
+ pkg_dir = os.path.dirname(os.path.abspath(__file__))
16
+ template_dir = os.path.join(pkg_dir, "templates")
17
+ static_dir = os.path.join(pkg_dir, "static")
18
+
19
+ app = Flask(__name__, template_folder=template_dir, static_folder=static_dir)
20
+
21
+ cf = Config("config.yaml")
22
+
23
+ # Init Pirate Bay
24
+ tpb = TPB(base_url=cf.get("tbp_url"))
25
+
26
+ player = Player()
27
+
28
+ # Init qBittorrent wrapper (API v4 by default)
29
+ qb = tr(url=cf.get("qb_url")) # qBittorrent default WebUI port is 8080
30
+
31
+ def get_local_ip():
32
+ try:
33
+ # Use hostname -I to get all IPs and prioritize the typical home Wi-Fi range
34
+ output = subprocess.check_output(['hostname', '-I'], text=True).strip()
35
+ ips = output.split()
36
+
37
+ # 1. Highest priority: 192.168.x.x (standard home router)
38
+ for ip in ips:
39
+ if ip.startswith("192.168."):
40
+ return ip
41
+
42
+ # 2. Secondary priority: other local subnets excluding common VPN/Docker defaults
43
+ for ip in ips:
44
+ if (ip.startswith("10.") and not ip.startswith("10.2.")) or \
45
+ (ip.startswith("172.") and not ip.startswith("172.17.")):
46
+ return ip
47
+
48
+ # 3. Fallback to the first valid IPv4 that isn't localhost
49
+ for ip in ips:
50
+ if "." in ip and not ip.startswith("127."):
51
+ return ip
52
+ except Exception:
53
+ pass
54
+
55
+ try:
56
+ # Last resort fallback: dummy UDP socket
57
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
58
+ s.settimeout(0.5)
59
+ s.connect(("8.8.8.8", 80))
60
+ ip = s.getsockname()[0]
61
+ s.close()
62
+ return ip
63
+ except:
64
+ return "127.0.0.1"
65
+
66
+ @app.route("/")
67
+ def index():
68
+ return render_template("index.html")
69
+
70
+ @app.route("/mydownloads")
71
+ def downloads_page():
72
+ return render_template("downloads.html")
73
+
74
+ @app.route("/controller")
75
+ def remote():
76
+ return render_template("controller.html")
77
+
78
+ @app.route("/api/movies")
79
+ def api_movies():
80
+ qs = request.query_string.decode('utf-8')
81
+ url = f"https://yts.lt/api/v2/list_movies.json?{qs}"
82
+ try:
83
+ req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
84
+ with urllib.request.urlopen(req, timeout=10) as response:
85
+ data = json.loads(response.read().decode('utf-8'))
86
+ return jsonify(data)
87
+ except Exception as e:
88
+ return jsonify({"status": "error", "message": str(e)}), 500
89
+
90
+ @app.route("/search")
91
+ def search():
92
+ query = request.args.get("q", "")
93
+ if not query:
94
+ return jsonify([])
95
+ torrents = tpb.search(query)
96
+ results = [{
97
+ "title": t.title,
98
+ "seeds": t.seeds,
99
+ "leeches": t.leeches,
100
+ "upload_date": t.upload_date,
101
+ "uploader": t.uploader,
102
+ "filesize": t.filesize,
103
+ "byte_size": t.byte_size,
104
+ "magnetlink": t.magnetlink,
105
+ "url": t.url,
106
+ "is_trusted": t.is_trusted,
107
+ "is_vip": t.is_vip,
108
+ "infohash": t.infohash,
109
+ "category": t.category
110
+ } for t in torrents]
111
+ return jsonify(results)
112
+
113
+ @app.route("/download", methods=["POST"])
114
+ def download():
115
+ data = request.get_json()
116
+ magnet = data.get("magnetlink")
117
+ if not magnet:
118
+ return jsonify({"error": "No magnetlink"}), 400
119
+
120
+ success = qb.download_torrent(magnet)
121
+ if success:
122
+ return jsonify({"status": "added"})
123
+ return jsonify({"status": "failed"}), 500
124
+
125
+ @app.route("/downloads")
126
+ def downloads():
127
+ try:
128
+ tasks = qb.torrent_status()
129
+ except Exception as e:
130
+ return jsonify({"error": str(e)}), 500
131
+
132
+ results = [{
133
+ "hash": d["hash"],
134
+ "name": d["name"],
135
+ "status": d["state"],
136
+ "progress": f"{d['progress']*100:.2f}%",
137
+ "speed": f"{d['dlspeed']/1024:.1f} KiB/s",
138
+ "eta": tr.format_eta(d["eta"])
139
+ } for d in tasks]
140
+ return jsonify(results)
141
+
142
+ @app.route('/watch/<name>')
143
+ def watch(name):
144
+ file = qb.get_file_path(name)
145
+ if not file:
146
+ return jsonify({"error": "File not found"}), 404
147
+
148
+ player.set_media(file)
149
+ player.play()
150
+
151
+ return jsonify({'error': None, 'file': file}), 200
152
+
153
+ @app.route('/remove/<hash>', methods=['DELETE'])
154
+ def remove(hash):
155
+ success = qb.delete(hash)
156
+ if success:
157
+ return jsonify({'error': None}), 200
158
+ return jsonify({'error': 'Failed to remove torrent'}), 500
159
+
160
+ @app.route("/pause/<hash>", methods=["POST"])
161
+ def pause(hash):
162
+ if not hash:
163
+ return jsonify({"error": "No hash"}), 400
164
+ qb.pause(hash)
165
+ return jsonify({"status": "paused"})
166
+
167
+ @app.route("/resume/<hash>", methods=["POST"])
168
+ def resume(hash):
169
+ if not hash:
170
+ return jsonify({"error": "No hash"}), 400
171
+ qb.resume(hash)
172
+ return jsonify({"status": "resumed"})
173
+
174
+ @app.route('/command/<cmd>', methods=['POST'])
175
+ def command(cmd):
176
+ if cmd == "plp":
177
+ player.pause()
178
+ if cmd == "stp":
179
+ print("STOPPING")
180
+ player.stop_and_close()
181
+ if cmd == "rrw":
182
+ player.rewind(60)
183
+ if cmd == "ffw":
184
+ player.fast_forward(60)
185
+ if cmd == "ccy":
186
+ player.enable_subtitles()
187
+ if cmd == "ccn":
188
+ player.disable_subtitles()
189
+
190
+
191
+ return jsonify({"status": "success"})
192
+
193
+ def setup():
194
+ qb.set_torrent_download_location(cf.get("download_folder"), create=cf.get("create_folder", True))
195
+
196
+ if not qb.check_connection():
197
+ raise Exception("Failed to connect to QBittorrent. Please check your settings and ensure QBittorrent is running.")
198
+
199
+ def main():
200
+ setup()
201
+
202
+ port = cf.get("port")
203
+ if not port:
204
+ port = 3000
205
+
206
+ app_url = f"http://{get_local_ip()}:{port}"
207
+ print(f"Starting overlay with URL: {app_url}")
208
+
209
+ # Calculate the path to the overlay script dynamically so it works anywhere
210
+ overlay_path = os.path.join(pkg_dir, "overlay.py")
211
+ subprocess.Popen([sys.executable, overlay_path, app_url])
212
+
213
+ app.run(debug=False, host=cf.get("host"), port=port)
214
+
215
+ if __name__ == "__main__":
216
+ main()
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env python3
2
+ import tkinter as tk
3
+ import sys
4
+ import time
5
+ import math
6
+ import random
7
+ from datetime import datetime
8
+
9
+ def get_ip_from_args():
10
+ if len(sys.argv) > 1:
11
+ return sys.argv[1]
12
+ return "No IP Provided"
13
+
14
+ class IPOverlay:
15
+ def __init__(self, root, ip_text):
16
+ self.root = root
17
+ self.ip_text = ip_text
18
+
19
+ # Configure window
20
+ root.title("PiRate Idle Screen")
21
+ root.geometry(f"{root.winfo_screenwidth()}x{root.winfo_screenheight()}+0+0")
22
+ root.overrideredirect(True)
23
+ root.attributes("-topmost", True)
24
+ root.configure(bg="#050505")
25
+ try:
26
+ root.attributes("-fullscreen", True)
27
+ except:
28
+ pass
29
+
30
+ root.config(cursor="none") # Hide mouse cursor
31
+
32
+ self.w = root.winfo_screenwidth()
33
+ self.h = root.winfo_screenheight()
34
+
35
+ # Main canvas for drawing
36
+ self.canvas = tk.Canvas(root, bg="#050505", highlightthickness=0, width=self.w, height=self.h)
37
+ self.canvas.pack(fill="both", expand=True)
38
+
39
+ # Create glowing orbs (cinematic Apple TV style)
40
+ self.orbs = []
41
+ # Dark, subtle ambient colors
42
+ colors = ["#1a0f2e", "#0f172a", "#2e0f1a"]
43
+ for i, color in enumerate(colors):
44
+ size = self.h * 1.5
45
+ x = self.w / 2
46
+ y = self.h / 2
47
+ orb = self.canvas.create_oval(x-size/2, y-size/2, x+size/2, y+size/2, fill=color, outline="")
48
+ self.orbs.append({"id": orb, "cx": x, "cy": y, "r": size/2, "offset": i*2.5})
49
+
50
+ # Create particles (stars/dust)
51
+ self.particles = []
52
+ for _ in range(80):
53
+ x = random.randint(0, self.w)
54
+ y = random.randint(0, self.h)
55
+ r = random.uniform(0.5, 1.8)
56
+ dx = random.uniform(-0.3, 0.3)
57
+ dy = random.uniform(-0.3, 0.3)
58
+ opacity = random.uniform(0.1, 0.6)
59
+ shade = int(255 * opacity)
60
+ color = f"#{shade:02x}{shade:02x}{shade:02x}"
61
+ p_id = self.canvas.create_oval(x-r, y-r, x+r, y+r, fill=color, outline="")
62
+ self.particles.append({"id": p_id, "x": x, "y": y, "dx": dx, "dy": dy, "r": r})
63
+
64
+ # Text fonts
65
+ font_time = ("Helvetica", max(72, int(self.h / 8)), "normal")
66
+ font_date = ("Helvetica", max(24, int(self.h / 30)), "normal")
67
+ font_large = ("Helvetica", max(48, int(self.h / 15)), "bold")
68
+ font_small = ("Helvetica", max(20, int(self.h / 35)))
69
+
70
+ # Time/Date overlay (top/center)
71
+ self.time_text = self.canvas.create_text(self.w/2, self.h/4, text="", font=font_time, fill="#ffffff")
72
+ self.date_text = self.canvas.create_text(self.w/2, self.h/4 + max(60, int(self.h/15)), text="", font=font_date, fill="#a1a1aa")
73
+
74
+ # Center text
75
+ self.title_text = self.canvas.create_text(self.w/2, self.h*0.65, text="PiRate Remote", font=font_large, fill="#ffffff")
76
+ self.url_text = self.canvas.create_text(self.w/2, self.h*0.65 + max(40, int(self.h/20)), text=f"Connect to: {self.ip_text}", font=font_small, fill="#38bdf8")
77
+
78
+ # Bindings to close
79
+ root.bind("<Key>", lambda e: root.destroy())
80
+ root.bind("<Button-1>", lambda e: root.destroy())
81
+
82
+ self._running = True
83
+ self._animate()
84
+ self._update_time()
85
+
86
+ def _update_time(self):
87
+ if not self._running:
88
+ return
89
+ now = datetime.now()
90
+ time_str = now.strftime("%I:%M %p").lstrip('0')
91
+ date_str = now.strftime("%A, %B %d")
92
+
93
+ self.canvas.itemconfig(self.time_text, text=time_str)
94
+ self.canvas.itemconfig(self.date_text, text=date_str)
95
+
96
+ # Bring all text to front
97
+ self.canvas.tag_raise(self.time_text)
98
+ self.canvas.tag_raise(self.date_text)
99
+ self.canvas.tag_raise(self.title_text)
100
+ self.canvas.tag_raise(self.url_text)
101
+
102
+ self.root.after(1000, self._update_time)
103
+
104
+ def _animate(self):
105
+ if not self._running:
106
+ return
107
+
108
+ t = time.time() * 0.15
109
+
110
+ # Slowly move the orbs around to create a breathing cinematic effect
111
+ for i, orb in enumerate(self.orbs):
112
+ dx = math.sin(t + orb["offset"]) * (self.w * 0.25)
113
+ dy = math.cos(t * 0.8 + orb["offset"]) * (self.h * 0.25)
114
+
115
+ x1 = orb["cx"] + dx - orb["r"]
116
+ y1 = orb["cy"] + dy - orb["r"]
117
+ x2 = orb["cx"] + dx + orb["r"]
118
+ y2 = orb["cy"] + dy + orb["r"]
119
+
120
+ self.canvas.coords(orb["id"], x1, y1, x2, y2)
121
+
122
+ # Move particles
123
+ for p in self.particles:
124
+ p["x"] += p["dx"]
125
+ p["y"] += p["dy"]
126
+
127
+ # wrap around
128
+ if p["x"] < 0: p["x"] = self.w
129
+ elif p["x"] > self.w: p["x"] = 0
130
+ if p["y"] < 0: p["y"] = self.h
131
+ elif p["y"] > self.h: p["y"] = 0
132
+
133
+ self.canvas.coords(p["id"], p["x"]-p["r"], p["y"]-p["r"], p["x"]+p["r"], p["y"]+p["r"])
134
+
135
+ self.root.after(40, self._animate)
136
+
137
+ def main():
138
+ ip = get_ip_from_args()
139
+ root = tk.Tk()
140
+ app = IPOverlay(root, ip)
141
+ root.mainloop()
142
+
143
+ if __name__ == "__main__":
144
+ main()
@@ -0,0 +1,77 @@
1
+ import vlc
2
+ import time
3
+ import os
4
+ from .logger import logging
5
+
6
+ class Player:
7
+ def __init__(self):
8
+ self.media_path = None
9
+ self.instance = vlc.Instance()
10
+ self.player = self.instance.media_player_new()
11
+
12
+ def set_media(self, media_path):
13
+ self.media_path = media_path
14
+ media = self.instance.media_new(media_path)
15
+ self.player.set_media(media)
16
+
17
+ def play(self):
18
+ if not self.media_path:
19
+ logging.error("No media file set!")
20
+ return
21
+ self.player.set_fullscreen(True)
22
+ self.player.play()
23
+ self.player.video_set_spu(-1)
24
+ return True
25
+
26
+ def pause(self):
27
+ if self.player.is_playing():
28
+ self.player.pause()
29
+ else:
30
+ self.player.play()
31
+
32
+ def fast_forward(self, seconds=10):
33
+ current_time = self.player.get_time()
34
+ self.player.set_time(current_time + (seconds * 1000))
35
+
36
+ def rewind(self, seconds=10):
37
+ current_time = self.player.get_time()
38
+ new_time = max(0, current_time - (seconds * 1000))
39
+ self.player.set_time(new_time)
40
+
41
+ def enable_subtitles(self):
42
+ spu_count = self.player.video_get_spu_count() # Get number of available subtitle tracks
43
+ logging.debug(f"Available subtitle tracks: {spu_count}")
44
+
45
+ if spu_count > 0:
46
+ spu_tracks = self.player.video_get_spu_description()
47
+ if len(spu_tracks) > 1:
48
+ second_track = spu_tracks[1][0] # Extract the second subtitle track ID
49
+ logging.debug(f"Enabling subtitles: Track {second_track}")
50
+ self.player.video_set_spu(second_track)
51
+ else:
52
+ logging.debug("Second subtitle track not available.")
53
+ else:
54
+ logging.debug("No subtitles available.")
55
+
56
+
57
+ def disable_subtitles(self):
58
+ self.player.video_set_spu(-1) # -1 disables subtitles
59
+
60
+ def stop_and_close(self):
61
+ self.player.stop()
62
+
63
+ def is_playing(self):
64
+ return self.player.is_playing()
65
+
66
+ if __name__ == "__main__":
67
+ player = Player()
68
+ player.set_media("example.mp4")
69
+ player.play()
70
+ time.sleep(5) # Let it play for 5 seconds
71
+ player.fast_forward(10) # Fast forward 10 seconds
72
+ time.sleep(5)
73
+ player.pause()
74
+ time.sleep(2)
75
+ player.play()
76
+ time.sleep(5)
77
+ player.stop_and_close()