3 import sys, os, argparse
7 from urllib.error import HTTPError
10 from errno import EPIPE
11 from sys import stderr
13 from iview.utils import encodeerrors
17 iview.comm.get_config()
18 except HTTPError as error:
20 Error: could not retrieve an important configuration file from iView.
21 Please make sure you are connected to the Internet.
23 If iView works fine in your web browser, the iView API has possibly changed.
24 Try to find an updated version of this program or contact the author.
26 Debug: could not load URL {}""".format(error.url), file=stderr)
30 """Downloads the iView index, and then prints each item in all series.
31 This is a slow function. Using index() and subsequently cherrypicking
32 a series to pass to series() is much faster.
34 return category('index')
36 def category(keyword):
38 index = iview.comm.get_keyword(keyword)
43 sys.stdout.write(encodeerrors(series['title'] + ':\n', sys.stdout))
44 print_series_items(series['items'], indent='\t')
47 """Downloads the iView index, and prints the corresponding series and IDs.
50 index = iview.comm.get_index()
55 title = encodeerrors(series['title'], sys.stdout)
56 sys.stdout.write('{}\t{}\n'.format(series['id'], title))
59 """Downloads the iView index, and prints the corresponding series and IDs in a format
60 that works in the iview batch file
63 index = iview.comm.get_index()
68 title = encodeerrors(series['title'], sys.stdout)
69 sys.stdout.write('{}: {}\n'.format(series['id'], title))
71 def series(series_id):
73 print_series_items(iview.comm.get_series_items(series_id))
75 def print_series_items(items, indent=''):
79 title = encodeerrors(item['title'], sys.stdout)
80 sys.stdout.write('{}{}\t({})\n'.format(indent, title, item['url']))
84 auth = iview.comm.get_auth()
85 print('iView auth data:')
87 ('Streaming Host', 'host'),
88 ('RTMP Token', 'token'),
89 ('HDS Token', 'tokenhd'),
90 ('Server URL', 'server'),
91 ('Playpath Prefix', 'playpath_prefix'),
92 ('Unmetered', 'free'),
96 print('\t{}: {}'.format(desc, value))
98 def download(url, output=None):
100 iview.fetch.fetch_program(url, execvp=True, dest_file=output)
102 def batch(batch_file):
105 # parse the batch file
106 batch = configparser.ConfigParser()
107 batch.read(os.path.expanduser(batch_file))
108 items = batch.items('batch')
110 batch_destination = '.'
114 # separate options from the series ids
115 for key, value in items:
116 if key == 'destination':
117 batch_destination = value
118 elif key == 'last_only':
119 if not(value == '0' or value.lower() == 'false' or value.lower() == "no"):
122 # Note: currently the value after the series_id in the batch file
123 # is only used as a comment for the user.
124 series_ids.append(key)
126 # move to where the files should be downloaded
127 os.chdir(batch_destination)
129 # loop through the series ids
130 for series_id in series_ids:
132 # unset the last episode for the current series.
135 # loop through the episodes
136 result = iview.comm.get_series_items(series_id, get_meta=True)
137 [episodes, metadata] = result
138 for episode in episodes:
140 # This last_only feature is experimental, I am not sure which field to
141 # use to determine the most recent episode.
142 if last_episode is None or episode['date'] > last_episode['date']:
143 last_episode = episode
145 batch_fetch_program(episode, series=metadata['title'])
147 # Last only means we only get one episode for the series
149 batch_fetch_program(last_episode, series=metadata['title'])
151 def batch_fetch_program(episode, series):
152 # Only print notification messages for episodes that have never been downloaded before.
155 # urls sometimes include a path like 'news/' or 'kids/'
156 (pathpart, filepart) = os.path.split(url)
158 # urls also have '.mp4' extension but downloaded files end in '.flv'
159 (base, ext) = os.path.splitext(filepart)
161 title = episode['title']
162 filename = iview.fetch.descriptive_filename(series, title, url)
163 if os.path.isfile(base + '.flv') and not os.path.isfile(filename):
164 print("{} already exists as {}.flv so should be moved".format(filename, base))
167 if not os.path.isfile(filename):
168 msg = "getting {} - {} -> {}".format(episode['title'], episode['url'], filename)
169 print(msg, file=stderr)
170 iview.fetch.fetch_program(episode['url'], execvp=False, dest_file=filename, quiet=True)
174 def subtitles(name, output=None):
177 url = name.rsplit('.', 1)[0]
178 if output is not None:
181 srt = url.rsplit('/', 1)[-1] + '.srt'
183 if not srt == '-' and os.path.isfile(srt):
184 print('Subtitles have already been downloaded to {}'.format(srt), file=stderr)
187 msg = 'Downloading subtitles to {}...'.format(srt)
188 print(msg, end=' ', file=stderr)
191 subtitles = iview.comm.get_captions(url)
192 except HTTPError as error:
193 print('failed', file=stderr)
194 print('Got an error when downloading {}'.format(error.url), file=stderr)
200 f = sys.stdout.detach()
204 f.write(subtitles.encode('utf-8'))
206 print('done', file=stderr)
208 def parse_proxy_argument(proxy):
209 """Try to parse 'proxy' as host:port pair. Returns an error message
210 if it cannot be understood. Otherwise, it configures the settings
211 in iview.config and returns None.
215 split = urllib.parse.SplitResult(scheme="", netloc=proxy,
216 path="", query="", fragment="")
217 if split.port is not None:
218 iview.config.socks_proxy_port = split.port
219 except ValueError as err:
221 iview.config.socks_proxy_host = split.hostname
226 params = argparse.ArgumentParser()
227 params.add_argument("-i", "--index", action="store_true",
228 help="print the iView index (number is the series ID)")
229 params.add_argument("-s", "--series", metavar="<id>",
230 help="get the list of programmes for the series")
231 params.add_argument("-k", "--category", metavar="<keyword>",
232 help="list programmes matching a category keyword")
233 params.add_argument("-p", "--programme", action="store_true",
234 help="""list all iView programmes at once""")
235 params.add_argument("-d", "--download", metavar="<url>",
236 help="""download a programme
237 (pass the url you got from -s, -k or -p)""")
238 params.add_argument("-t", "--subtitles" , metavar="<url>",
239 help="""download subtitles in SRT format for a programme
240 (pass the same url as for --download)""")
241 params.add_argument("-o", "--output", metavar="<file>",
242 help="specify a file to output to (use - for stdout)")
243 params.add_argument("--batch", metavar="<file>",
244 help="specify a batch operation file (for cronjob etc)")
245 params.add_argument("--bindex", action="store_true",
246 help="like --index but output is in batchfile format")
247 params.add_argument("-a", "--print-auth", action="store_true",
248 help="print debug iView auth information")
249 params.add_argument("-c", "--cache", metavar="<dir>",
250 help="use cache directory for debugging")
251 params.add_argument("--host", metavar="<name>",
252 help="override streaming host")
253 params.add_argument("--ip", metavar="<address>",
254 help="send IP address in auth request")
255 params.add_argument("-x", "--proxy", metavar="<host:port>",
256 help="use specified SOCKS proxy")
258 if len(sys.argv) <= 1:
259 params.print_help(stderr)
261 args = params.parse_args()
263 if args.proxy is not None:
264 err = parse_proxy_argument(args.proxy)
266 print("Invalid proxy specification: {}\n".format(err), file=stderr)
267 params.print_usage(stderr)
269 iview.comm.configure_socks_proxy()
270 if args.cache is not None:
271 iview.config.cache = args.cache
272 if args.host is not None:
273 iview.config.override_host = args.host
274 if args.ip is not None:
275 iview.config.ip = args.ip
280 if args.category is not None:
281 category(args.category)
286 if args.series is not None:
291 if args.download is not None:
292 download(args.download, args.output)
293 elif args.subtitles is not None:
294 subtitles(args.subtitles, args.output)
295 elif args.batch is not None:
297 except iview.comm.Error as error:
298 print(error, file=stderr)
301 if __name__ == "__main__":
304 except KeyboardInterrupt:
306 except EnvironmentError as err:
307 if err.errno == EPIPE: