+# 0.9.0
+
+- Replace youtube-dl with yt-dlp. Though this fixes the problem with YouTube throttling download speeds, it doesn't fix the throttling during streaming since mpv and vlc are still using youtube-dl. mpv has provided an option to provide an alternative youtube-dl implementation. Waiting for VLC to implement the same.
+- Shift to PeerTube's new videoId format. Support for the UUID format is dropped.
+- Dropped Peerflix as a dependency. WebTorrent is faster and better. They also added support for video player arguments on my request. See https://github.com/webtorrent/webtorrent-cli/issues/141
+
# 0.8.3
- Add fake user-agent
-# NimCoon
+# Nimcoon
Play videos from YouTube and PeerTube from the command line using your preferred desktop media player.
YouTube's business incentive is to make you watch as many videos as possible. If you open the YouTube website and are logged into it, you will just get distracted by recommendations and forget why you opened it in the first place. You might have wanted to watch a conference talk but end up going down a rabbit hole of other "interesting" videos customized for you.
-NimCoon has a spartan design. It doesn't even show images of the search results. It doesn't let you browse YouTube. You have to explicitly search for something.
+Nimcoon has a spartan design. It doesn't even show images of the search results. It doesn't let you browse YouTube. You have to explicitly search for something.
I have had better success with managing my YouTube consumption after shifting to this tool. Settings inspired by Pinafore's wellness settings.
## Installation
Nim Coon depends on the following:
-- youtube-dl
+- yt-dlp
- mpv (recommended) or vlc
-- peerflix and webtorrent (for magnet links)
+- webtorrent (for magnet links)
Install MPV or VLC using your distribution's package manager.
-Install YouTube-dl
+Install yt-dlp
``` sh
-pip3 install --user youtube-dl
+pip3 install --user yt-dlp
```
-Install PeerFlix and WebTorrent
+Install WebTorrent
```sh
-npm install --global peerflix webtorrent-cli
+npm install --global webtorrent-cli
```
(Optional) If you want your YouTube downloads to be faster, install `aria2` download manager.
### Installing using Nimble
-NimCoon can be installed from Nimble repositories:
+Nimcoon can be installed from Nimble repositories:
``` sh
nimble install nimcoon
### Command line arguments
-NimCoon provides both interactive and non-interactive arguments with significant
+Nimcoon provides both interactive and non-interactive arguments with significant
overlap. But some arguments might only be present in one mode.
Non-interactive arguments are specified to the nimcoon program and apply
| -d, --download | Download video or music |
| -a, --auto-play | Play the next search result (YouTube only) |
-Feel free to use these options in any combination. NimCoon will show a helpful
+Feel free to use these options in any combination. Nimcoon will show a helpful
error message if you pick incompatible options.
Interactive arguments are provided during selection of a search result. These
| -d, --download | Download video or music |
| -a, --auto-play | Play the next search result (YouTube only) |
-Auto-playing videos leads to binge watching. The default option in NimCoon is to
+Auto-playing videos leads to binge watching. The default option in Nimcoon is to
support auto-play for music only.
## Development
- [ ] Better CLI using curses - select with arrows and press enter
- illwill library can be used
- see nim-mod project as an example
+- [ ] Fix JSON parsing error thrown during search
;;; Non-interactive functions to respond to URL clicks
(defun nimcoon-play-url (url &rest args)
- "Play given URL in NimCoon."
+ "Play given URL in Nimcoon."
(start-process "nimcoon" nil "nimcoon" url))
-;; Play all YouTube URLs in NimCoon
+;; Play all YouTube URLs in Nimcoon
(setq browse-url-browser-function
(quote
(("youtu\\.?be" . nimcoon-play-url)
("." . browse-url-default-browser))))
(defun run-nimcoon(args query)
- "Search by QUERY and play in NimCoon."
+ "Search by QUERY and play in Nimcoon."
(call-process "nimcoon" nil 0 nil args query))
(defun nimcoon-search(args query)
"Search by QUERY with the given ARGS."
- (with-output-to-temp-buffer "*NimCoon search results*"
- (call-process "nimcoon" nil "*NimCoon search results*" t args query)
- (with-current-buffer "*NimCoon search results*"
+ (with-output-to-temp-buffer "*Nimcoon search results*"
+ (call-process "nimcoon" nil "*Nimcoon search results*" t args query)
+ (with-current-buffer "*Nimcoon search results*"
(org-mode))))
;;; Interactive functions
(interactive "sSearch query: ")
(run-nimcoon "-dlm" query))
-;; Assumes only one process exists. Must capture the pid of the running NimCoon process and only kill it.
+;; Assumes only one process exists. Must capture the pid of the running Nimcoon process and only kill it.
(defun nimcoon-kill-background-processes()
- "Kill NimCoon process running in the background. Useful for stopping background music."
+ "Kill Nimcoon process running in the background. Useful for stopping background music."
(interactive)
(shell-command "kill `pgrep nimcoon` `pgrep mpv` `pgrep vlc`"))
;;; Keybindings
(map! :leader
- ;;; <leader> N --- NimCoon
- (:prefix-map ("N" . "NimCoon")
+ ;;; <leader> N --- Nimcoon
+ (:prefix-map ("N" . "Nimcoon")
(:prefix ("d" . "Download")
:desc "Video" "v" #'nimcoon-download-video
:desc "Music" "m" #'nimcoon-download-music)
# Package
-version = "0.8.3"
+version = "0.9.0"
author = "Joseph Nuthalapati"
-description = "A command-line YouTube player and more"
+description = "A command-line player for YouTube, PeerTube and more."
license = "GPL-3.0"
srcDir = "src"
bin = @["nimcoon"]
let
processOptions = {poStdErrToStdOut, poUsePath} # Add poEchoCmd to debug
- 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}"
+ PEERTUBE_REGEX = re"w\/[0-9a-zA-z]{22}"
proc isInstalled(program: string): bool =
proc directPlay*(url: string, player: string, options: Table[string, bool]) =
let url =
if find(url, PEERTUBE_REGEX) != -1 and "webtorrent".isInstalled:
- getPeerTubeMagnetLink(url)
+ getPeerTubeMagnetLink(url, options["musicOnly"])
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")
+ discard execShellCmd(&"webtorrent '{url}' --{player} --player-args='--no-video'")
else:
- # WebTorrent is so much faster!
discard execProcess("webtorrent", args=[url, &"--{player}"], options=processOptions)
else:
play(player, options, url)
proc download*(args: openArray[string], title: string) =
printTitle("Downloading", title)
- discard execShellCmd(&"youtube-dl {args.join(\" \")}")
+ discard execShellCmd(&"yt-dlp {args.join(\" \")}")
proc directDownload*(url: string, options: Options) =
let args = buildDownloadArgs(url, options)
if "aria2c".isInstalled:
- discard execShellCmd(&"youtube-dl {args.join(\" \")} --external-downloader aria2c --external-downloader-args '-x 16 -s 16 -k 2M'")
+ discard execShellCmd(&"yt-dlp {args.join(\" \")} --external-downloader aria2c --external-downloader-args '-x 16 -s 16 -k 2M'")
else:
- discard execShellCmd(&"youtube-dl {args.join(\" \")}")
+ discard execShellCmd(&"yt-dlp {args.join(\" \")}")
proc luckyDownload*(searchQuery: string, options: Options) =
- let args = @[&"ytsearch:\"{searchQuery}\""] & buildDownloadArgs("", options)
- let title = execProcess(&"youtube-dl --get-title {args.join(\" \")}").split("\n")[0]
+ let args = @[&"ytsearch1:\"{searchQuery}\""] & buildDownloadArgs("", options)
+ let title = execProcess(&"yt-dlp --get-title {args.join(\" \")}").split("\n")[0]
download(args, title)
proc luckyPlay*(searchQuery: string, player: string, options: Options) =
let args = @[&"ytsearch:\"{searchQuery}\""] & buildDownloadArgs("", options)
- let output = execProcess(&"youtube-dl --get-url --get-title {args.join(\" \")}").split("\n")
+ let output = execProcess(&"yt-dlp --get-url --get-title {args.join(\" \")}").split("\n")
let
title = output[0]
url = &"\"{output[1]}\""
of cmdEnd: discard
if searchQuery == "":
- stderr.writeLine "NimCoon doesn't permit browsing. You must provide a search query."
+ stderr.writeLine "Nimcoon doesn't permit browsing. You must provide a search query."
quit(1)
(searchQuery, options)
strformat,
strutils
-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}"
+let PEERTUBE_REGEX = re"w\/[0-9a-zA-z]{22}"
-proc getPeerTubeMagnetLink*(url: string): string =
+proc getPeerTubeMagnetLink*(url: string, musicOnly: bool): string =
## Gets the magnet link of the best possible resolution from PeerTube
- let uuid = url.substr(find(url, PEERTUBE_REGEX) + "videos/watch/".len)
+ let uuid = url.substr(find(url, PEERTUBE_REGEX) + "w/".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()
+ var files = jsonNode["files"]
+ if len(jsonNode["files"]) == 0:
+ files = jsonNode["streamingPlaylists"][0]["files"]
+ if musicOnly:
+ files[len(files)-1]["magnetUri"].getStr()
+ else:
+ files[0]["magnetUri"].getStr()