import
htmlparser,
httpClient,
+ json,
os,
osproc,
+ re,
sequtils,
- sugar,
- strformat,
std/[terminal],
strformat,
+ strformat,
strtabs,
strutils,
+ sugar,
tables,
uri,
xmltree
# poEchoCmd can be added to options for debugging
let processOptions = {poStdErrToStdOut, poUsePath}
+let PEERTUBE_REGEX = re"videos\/watch\/[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}"
+
+proc isInstalled(program: string): bool =
+ execProcess("which " & program).len != 0
proc selectMediaPlayer*(): string =
- let availablePlayers = filterIt(supportedPlayers, execProcess("which " & it).len != 0)
+ let availablePlayers = supportedPlayers.filter(isInstalled)
if len(availablePlayers) == 0:
stderr.writeLine &"Please install one of the supported media players: {supportedPlayers}"
raise newException(OSError, "No supported media player found")
let response = get(client, &"https://www.youtube.com/results?hl=en&search_query={queryParam}")
return $response.body
+proc getPeerTubeMagnetLink(url: string): string =
+ # Gets the magnet link of the best possible resolutino from PeerTube
+ let uuid = url.substr(find(url, PEERTUBE_REGEX) + "videos/watch/".len)
+ let domainName = url.substr(8, find(url, '/', start=8) - 1)
+ let apiURL = &"https://{domainName}/api/v1/videos/{uuid}"
+ let client = newHttpClient()
+ let response = get(client, apiURL)
+ let jsonNode = parseJson($response.body)
+ jsonNode["files"][0]["magnetUri"].getStr()
+
func extractTitlesAndUrls*(html: string): SearchResults =
{.noSideEffect.}:
parseHtml(html).findAll("a").
# This is a pure function with no side effects
func buildPlayerArgs(url: string, options: Table[string, bool], player: string): seq[string] =
- var args = @[url]
- if options["musicOnly"]: args.add("--no-video")
- if options["fullScreen"]: args.add("--fullscreen")
- # Playlists are only supported for MPV player
- if isPlaylist(url) and player == "mpv":
- let list_arg = url.split('&')[1]
- args[0] = "https://www.youtube.com/playlist?" & list_arg
- return args
+ let url =
+ # Playlists are only supported for MPV player
+ if isPlaylist(url) and player == "mpv":
+ "https://www.youtube.com/playlist?" & url.split('&')[1]
+ else: url
+ let musicOnly = if options["musicOnly"]: "--no-video" else: ""
+ let fullScreen = if options["fullScreen"]: "--fullscreen" else: ""
+ return filterIt([url, musicOnly, fullScreen], it != "")
proc play*(player: string, options: Table[string, bool], url: string, title: string = "") =
let args = buildPlayerArgs(url, options, player)
func buildMusicDownloadArgs*(url: string): seq[string] =
{.noSideEffect.}:
- var args = @["--ignore-errors", "-f", "bestaudio", "--extract-audio", "--audio-format", "mp3", "--audio-quality", "0", "-o"]
let downloadLocation = &"'{expandTilde(musicDownloadDirectory)}/%(title)s.%(ext)s'"
- args.add(downloadLocation)
- args.add(url)
- return args
+ return @["--ignore-errors", "-f", "bestaudio", "--extract-audio", "--audio-format", "mp3", "--audio-quality", "0", "-o", downloadLocation, url]
func buildVideoDownloadArgs*(url: string): seq[string] =
{.noSideEffect.}:
- var args = @["-f", "best", "-o"]
let downloadLocation = &"'{expandTilde(videoDownloadDirectory)}/%(title)s.%(ext)s'"
- args.add(downloadLocation)
- args.add(url)
- return args
+ return @["-f", "best", "-o", downloadLocation, url]
proc download*(args: openArray[string], title: string) =
styledEcho "\n", fgGreen, "Downloading ", styleBright, fgMagenta, title
urlLongen(stripZshEscaping(url))
proc directPlay*(url: string, player: string, options: Table[string, bool]) =
- if url.startswith("magnet:"):
+ let url =
+ if find(url, PEERTUBE_REGEX) != -1 and isInstalled("webtorrent"):
+ getPeerTubeMagnetLink(url)
+ else: url
+ if url.startswith("magnet:") or url.endswith(".torrent"):
if options["musicOnly"]:
# TODO Replace with WebTorrent once it supports media player options
discard execShellCmd(&"peerflix '{url}' -a --{player} -- --no-video")