-
Notifications
You must be signed in to change notification settings - Fork 4
/
caster.py
executable file
·180 lines (143 loc) · 5.39 KB
/
caster.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import argparse
try:
from http.server import BaseHTTPRequestHandler, HTTPServer
except ImportError:
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import threading
import socket
import os
from socketserver import ThreadingMixIn
import mimetypes
import pychromecast
import readchar
def get_internal_ip(dst_ip):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect((dst_ip, 0))
return s.getsockname()[0]
def get_seek_time(time_str):
time_splited = time_str.split(':')
time = 0
if len(time_splited) >= 1:
time += int(time_splited[-1])
if len(time_splited) >= 2:
time += 60 * int(time_splited[-2])
if len(time_splited) >= 3:
time += 3600 * int(time_splited[-3])
return time
mimetypes.add_type("text/vtt", ".vtt")
class RequestHandler(BaseHTTPRequestHandler):
chunk_size = 1024
""" Handle HTTP requests for files which do not need transcoding """
def parse_range(self, file_size):
start = self.headers["range"].split("=")[1].split("-")[0]
end = file_size - 1
return int(start), int(end)
def do_GET(self):
self.protocol_version = "HTTP/1.1"
content_type = mimetypes.guess_type(self.path)
with open(self.path, "rb") as f:
f.seek(0, 2)
file_size = f.tell()
if "range" in self.headers:
self.send_response(206)
start_range, end_range = self.parse_range(file_size)
self.send_header('Content-Range', 'bytes {START}-{END}/{TOTAL}'.format(
START=start_range, END=end_range, TOTAL=file_size))
self.send_header('Accept-Ranges', 'bytes')
else:
self.send_response(200)
start_range, end_range = 0, file_size
self.send_header('Content-Length', end_range - start_range + 1)
self.send_header("Content-type", content_type[0])
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
f.seek(start_range)
curr = start_range
while True:
if curr + self.chunk_size > end_range:
read_size = end_range - curr
else:
read_size = self.chunk_size
curr += read_size
data = f.read(read_size)
self.wfile.write(data)
if curr == end_range:
break
def handle_one_request(self):
try:
return BaseHTTPRequestHandler.handle_one_request(self)
except socket.error:
pass
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
pass
def handle_input(server_thread, dev, mc):
while server_thread.is_alive():
key = readchar.readkey()
if key in [readchar.key.CTRL_C, "s"]:
mc.stop()
return
elif key == readchar.key.SPACE:
if mc.status.player_is_playing:
mc.pause()
else:
mc.play()
elif key == readchar.key.UP:
dev.volume_up()
elif key == readchar.key.DOWN:
dev.volume_down()
elif key == readchar.key.RIGHT:
mc.update_status()
mc.seek(mc.status.current_time + 30)
elif key == readchar.key.LEFT:
mc.update_status()
mc.seek(mc.status.current_time - 30)
elif key == "m":
if dev.status.volume_muted:
dev.set_volume_muted(False)
else:
dev.set_volume_muted(True)
def get_args():
parser = argparse.ArgumentParser(description='Caster - cast media to chromecast')
parser.add_argument('file', help='The file to play')
parser.add_argument('--device', help='The chromecast device to use.'
' When not given first one found is used.',
default=None)
parser.add_argument('--subtitles', help='subtitles', default=None)
parser.add_argument('--seek', help='media starting position in HH:MM:SS format', default="00:00:00")
return parser.parse_args()
def main():
args = get_args()
file_path = args.file
device_name = args.device
subs = args.subtitles
seek = get_seek_time(args.seek)
chromecasts = pychromecast.get_chromecasts()
if device_name:
dev = next(cc for cc in chromecasts if cc.device.friendly_name == device_name)
else:
dev = chromecasts[0]
dev.wait()
try:
server_ip = get_internal_ip(dev.host)
except Exception:
# See https://github.com/palaviv/caster/issues/1
server_ip = "0.0.0.0"
server = ThreadedHTTPServer((server_ip, 0), RequestHandler)
server_thread = threading.Thread(target=server.serve_forever)
server_thread.start()
mc = dev.media_controller
if subs:
subtitles_url = "http://{IP}:{PORT}/{URI}".format(IP=server_ip, PORT=server.server_port, URI=subs)
else:
subtitles_url = None
media_url = "http://{IP}:{PORT}/{URI}".format(IP=server_ip, PORT=server.server_port, URI=file_path)
mc.play_media(media_url, 'video/mp4', title=os.path.basename(file_path), subtitles=subtitles_url,
current_time=seek)
mc.update_status()
mc.enable_subtitle(1)
handle_input(server_thread, dev, mc)
server.shutdown()
server_thread.join()
server.server_close()
if __name__ == "__main__":
main()