def _download_clip(slug, args): print_out("<dim>Looking up clip...</dim>") clip = twitch.get_clip(slug) if not clip: raise ConsoleError("Clip '{}' not found".format(slug)) print_out( "Found: <green>{}</green> by <yellow>{}</yellow>, playing <blue>{}</blue> ({})" .format(clip["title"], clip["broadcaster"]["displayName"], clip["game"]["name"], utils.format_duration(clip["durationSeconds"]))) url = _get_clip_url(clip, args) print_out("<dim>Selected URL: {}</dim>".format(url)) url_path = urlparse(url).path extension = Path(url_path).suffix filename = "{}_{}{}".format(clip["broadcaster"]["login"], utils.slugify(clip["title"]), extension) print_out("Downloading clip...") download_file(url, filename) print_out("Downloaded: {}".format(filename))
def _continue(): print_out("\nThere are more videos. " "Press <green><b>Enter</green> to continue, " "<yellow><b>Ctrl+C</yellow> to break.") try: input() except KeyboardInterrupt: return False return True
def _select_playlist_interactive(playlists): print_out("\nAvailable qualities:") for n, (name, resolution, uri) in enumerate(playlists): print_out("{}) {} [{}]".format(n + 1, name, resolution)) no = utils.read_int("Choose quality", min=1, max=len(playlists) + 1, default=1) _, _, uri = playlists[no - 1] return uri
def _get_game_ids(names): if not names: return [] game_ids = [] for name in names: print_out("<dim>Looking up game '{}'...</dim>".format(name)) game_id = twitch.get_game_id(name) if not game_id: raise ConsoleError("Game '{}' not found".format(name)) game_ids.append(int(game_id)) return game_ids
def _join_vods(playlist_path, target, overwrite): command = [ "ffmpeg", "-i", playlist_path, "-c", "copy", target, "-stats", "-loglevel", "warning", ] if overwrite: command.append("-y") print_out("<dim>{}</dim>".format(" ".join(command))) result = subprocess.run(command) if result.returncode != 0: raise ConsoleError("Joining files failed")
def _get_clip_url(clip, args): # done qualities = clip["videoQualities"] # Quality given as an argument if args["quality"]: if args["quality"] == "source": return qualities[0]["sourceURL"] if args["quality"] == "worst": return qualities[-1]["sourceURL"] selected_quality = args["quality"].rstrip( "p") # allow 720p as well as 720 for q in qualities: if q["quality"] == selected_quality: return q["sourceURL"] available = ", ".join([str(q["quality"]) for q in qualities]) msg = "Quality '{}' not found. Available qualities are: {}".format( args["quality"], available) raise ConsoleError(msg) # Ask user to select quality print_out("\nAvailable qualities:") for n, q in enumerate(qualities): print_out("{}) {} [{} fps]".format(n + 1, q["quality"], q["frameRate"])) print_out() no = utils.read_int("Choose quality", min=1, max=len(qualities), default=1) selected_quality = qualities[no - 1] return selected_quality["sourceURL"]
def videos(args): game_ids = _get_game_ids(args["game"]) print_out("<dim>Loading videos...</dim>") generator = twitch.channel_videos_generator(args["channel_name"], args["limit"], args["sort"], args["type"], game_ids=game_ids) first = 1 for videos, has_more in generator: count = len(videos["edges"]) if "edges" in videos else 0 total = videos["totalCount"] last = first + count - 1 print_out("-" * 80) print_out("<yellow>Showing videos {}-{} of {}</yellow>".format( first, last, total)) for video in videos["edges"]: print_video(video["node"], file=args["filename"], path=args["path"]) if not args["pager"]: print_out( "\n<dim>There are more videos. " "Increase the --limit or use --pager to see the rest.</dim>") break if not has_more or not _continue(): break first += count else: print_out("<yellow>No videos found</yellow>")
def _download_video(video_id, args): # done if args["start"] and args["end"] and args["end"] <= args["start"]: raise ConsoleError("End time must be greater than start time") print_out("<dim>Looking up video...</dim>") video = twitch.get_video(video_id) print_out("Found: <blue>{}</blue> by <yellow>{}</yellow>".format( video['title'], video['channel']['display_name'])) print_out("<dim>Fetching access token...</dim>") access_token = twitch.get_access_token(video_id) print_out("<dim>Fetching playlists...</dim>") playlists_m3u8 = twitch.get_playlists(video_id, access_token) playlists = list(_parse_playlists(playlists_m3u8)) playlist_uri = (_get_playlist_by_name(playlists, args["quality"]) if args["quality"] else _select_playlist_interactive(playlists)) print_out("<dim>Fetching playlist...</dim>") response = requests.get(playlist_uri) response.raise_for_status() playlist = m3u8.loads(response.text) base_uri = re.sub("/[^/]+$", "/", playlist_uri) target_dir = _crete_temp_dir(base_uri) vod_paths = _get_vod_paths(playlist, args["start"], args["end"]) # Save playlists for debugging purposes with open(path.join(target_dir, "playlists.m3u8"), "w") as f: f.write(playlists_m3u8) with open(path.join(target_dir, "playlist.m3u8"), "w") as f: f.write(response.text) print_out("\nDownloading {} VODs using {} workers to {}".format( len(vod_paths), args["max_workers"], target_dir)) path_map = download_files(base_uri, target_dir, vod_paths, args["max_workers"]) # Make a modified playlist which references downloaded VODs # Keep only the downloaded segments and skip the rest org_segments = playlist.segments.copy() playlist.segments.clear() for segment in org_segments: if segment.uri in path_map: segment.uri = path_map[segment.uri] playlist.segments.append(segment) playlist_path = path.join(target_dir, "playlist_downloaded.m3u8") playlist.dump(playlist_path) if args["no_join"]: print_out("\n\n<dim>Skipping joining files...</dim>") print_out("VODs downloaded to:\n<blue>{}</blue>".format(target_dir)) return print_out("\n\nJoining files...") target = _video_target_filename(video, args) _join_vods(playlist_path, target, args["overwrite"]) if args["keep"]: print_out( "\n<dim>Temporary files not deleted: {}</dim>".format(target_dir)) else: print_out("\n<dim>Deleting temporary files...</dim>") shutil.rmtree(target_dir) print_out("\nDownloaded: <green>{}</green>".format(target))