├── LICENSE ├── README.md ├── index.js ├── lib ├── ascii-video.js ├── ffmpeg.js └── pcm-audio.js ├── package-lock.json └── package.json /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mathias Rasmussen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YouTube Terminal 2 | [![npm](https://img.shields.io/npm/v/youtube-terminal.svg)](https://npm.im/youtube-terminal) 3 | ![downloads](https://img.shields.io/npm/dt/youtube-terminal.svg) 4 | [![dependencies](https://david-dm.org/mathiasvr/youtube-terminal.svg)](https://david-dm.org/mathiasvr/youtube-terminal) 5 | [![license](https://img.shields.io/:license-MIT-blue.svg)](https://mvr.mit-license.org) 6 | 7 | Stream YouTube videos as ascii art in the terminal! 8 | 9 | ## usage 10 | 11 | YouTube Terminal will play the first found search result: 12 | 13 | ```shell 14 | $ youtube-terminal [options] 'cyanide and happiness' 15 | ``` 16 | 17 | ### options 18 | -l, --link [url] Use YouTube link instead of searching 19 | -i, --invert Invert brightness, recommended on white background 20 | --color Use 16 terminal colors [experimental] 21 | -c, --contrast [percent] Adjust video contrast [default: 35] 22 | -w, --width [number] ASCII video character width 23 | -m, --mute Disable audio playback 24 | --fps [number] Adjust playback frame rate 25 | -h, --help Display this usage information 26 | 27 | > Note that setting the `--invert` flag had the opposite effect in earlier releases, and was changed based on [this poll](https://github.com/mathiasvr/youtube-terminal/tree/v0.5.2#which-background-color-does-your-terminal-have). 28 | 29 | ## install 30 | 31 | ```shell 32 | $ npm install -g youtube-terminal 33 | ``` 34 | 35 | Be sure to have [FFmpeg](https://www.ffmpeg.org) installed as well. 36 | 37 | Ubuntu/Debian users should have ALSA installed as well: 38 | ```shell 39 | $ sudo apt-get install libasound2-dev 40 | ``` 41 | 42 | ## related 43 | 44 | [ascii-pixels](https://github.com/mathiasvr/ascii-pixels) 45 | 46 | ## license 47 | 48 | MIT 49 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var ytdl = require('ytdl-core') 4 | var Speaker = require('speaker') 5 | var youtubeSearch = require('youtube-crawler') 6 | var debug = require('debug')('yt-term') 7 | var pcmAudio = require('./lib/pcm-audio') 8 | var asciiVideo = require('./lib/ascii-video') 9 | 10 | // command line options 11 | var argv = require('minimist')(process.argv.slice(2), { 12 | // TODO: maybe use c to alias color instead of contrast 13 | alias: { l: 'link', i: 'invert', c: 'contrast', w: 'width', m: 'mute', h: 'help' }, 14 | boolean: ['invert', 'mute', 'color', 'help'] 15 | }) 16 | 17 | var wrongType = typeof argv.c === 'string' || typeof argv.w === 'string' 18 | 19 | if (argv.help || (argv._.length <= 0 && !argv.link) || wrongType) { 20 | printUsage() 21 | } else if (argv.link) { 22 | // play from youtube link 23 | console.log('Playing:', argv.link) 24 | play(argv.link) 25 | } else { 26 | // search youtube and play the first result 27 | var query = argv._.join(' ') 28 | youtubeSearch(query, function (err, results) { 29 | if (err) return console.error(err) 30 | console.log('Playing:', results[0].title) 31 | play(results[0].link) 32 | }) 33 | } 34 | 35 | function play (url) { 36 | ytdl.getInfo(url, function (err, info) { 37 | if (err) return console.error(err) 38 | 39 | if (!argv.mute) playAudio(info) 40 | 41 | playVideo(info) 42 | }) 43 | } 44 | 45 | function playVideo (info) { 46 | // low resolution video only, webm prefered 47 | var videoItems = info.formats 48 | .filter(function (format) { return format.resolution === '144p' && format.audioBitrate === null }) 49 | .sort(function (a, b) { return a.container === 'webm' ? -1 : 1 }) 50 | 51 | // lowest resolution 52 | var video = videoItems[0] 53 | 54 | debug('Video format: %s [%s] (%s) %sfps', video.resolution, video.size, video.encoding, video.fps) 55 | 56 | // TODO: maybe it is needed to null-check video.size and m, and default to '256x144' 57 | var size = video.size.match(/^(\d+)x(\d+)$/) 58 | 59 | var videoInfo = { 60 | url: video.url, 61 | width: size[1], 62 | height: size[2] 63 | } 64 | 65 | var videoOptions = { 66 | // TODO: some (old?) videos have fps incorrectly set to 1. 67 | fps: argv.fps /* || video.fps */ || 12, 68 | // TODO: width does not work well if video height is larger than terminal window 69 | width: argv.width || process.stdout.columns || 80, 70 | contrast: (argv.contrast || 35) * 2.55, // percent to byte 71 | invert: !argv.invert, 72 | color: argv.color 73 | } 74 | 75 | // play video as ascii 76 | asciiVideo(videoInfo, videoOptions) 77 | } 78 | 79 | function playAudio (info) { 80 | // audio only, sorted by quality 81 | var audioItems = info.formats 82 | .filter(function (format) { return format.resolution === null }) 83 | .sort(function (a, b) { return b.audioBitrate - a.audioBitrate }) 84 | 85 | // highest bitrate 86 | var audio = audioItems[0] 87 | 88 | debug('Audio quality: %s (%s)', audio.audioBitrate + 'kbps', audio.audioEncoding) 89 | 90 | var speaker = new Speaker() 91 | 92 | var updateSpeaker = function (codec) { 93 | speaker.channels = codec.audio_details[2] === 'mono' ? 1 : 2 94 | speaker.sampleRate = parseInt(codec.audio_details[1].match(/\d+/)[0], 10) 95 | } 96 | 97 | // play audio 98 | pcmAudio(audio.url).on('codecData', updateSpeaker).pipe(speaker) 99 | } 100 | 101 | function printUsage () { 102 | console.log('Youtube Terminal v' + require('./package.json').version) 103 | console.log() 104 | console.log('Usage: youtube-terminal [options] "search query"') 105 | console.log() 106 | console.log('Options:') 107 | console.log() 108 | console.log(' -l, --link [url] Use YouTube link instead of searching') 109 | console.log(' -i, --invert Invert brightness, recommended on white background') 110 | console.log(' --color Use 16 terminal colors [experimental]') 111 | console.log(' -c, --contrast [percent] Adjust video contrast [default: 35]') 112 | console.log(' -w, --width [number] ASCII video character width') 113 | console.log(' -m, --mute Disable audio playback') 114 | console.log(' --fps [number] Adjust playback frame rate') 115 | console.log(' -h, --help Display this usage information') 116 | console.log() 117 | process.exit(0) 118 | } 119 | -------------------------------------------------------------------------------- /lib/ascii-video.js: -------------------------------------------------------------------------------- 1 | var ffmpeg = require('./ffmpeg') 2 | var through2 = require('through2') 3 | var TokenBucket = require('limiter').TokenBucket 4 | var asciiPixels = require('ascii-pixels') 5 | var ChopStream = require('chop-stream') 6 | var charm = require('charm') 7 | 8 | function ThrottleStream (chunksPerSecond) { 9 | var bucket = new TokenBucket(chunksPerSecond, chunksPerSecond, 'second', null) 10 | 11 | return through2(function (chunk, _, next) { 12 | bucket.removeTokens(1, function (err) { 13 | next(err, chunk) 14 | }) 15 | }) 16 | } 17 | 18 | function AsciiStream (frameWidth, frameHeight, options) { 19 | return through2(function (frameData, _, next) { 20 | var imageData = { 21 | data: frameData, 22 | width: frameWidth, 23 | height: frameHeight, 24 | format: 'RGB24' 25 | } 26 | 27 | // convert to ascii art 28 | var ascii = asciiPixels(imageData, options) 29 | 30 | next(null, ascii) 31 | }) 32 | } 33 | 34 | function TerminalStream () { 35 | var term = charm(process) 36 | 37 | term.removeAllListeners('^C') 38 | term.on('^C', function () { 39 | term.cursor(true).end() 40 | process.exit() 41 | }) 42 | 43 | term.reset() 44 | term.cursor(false) 45 | 46 | return through2(function (text, _, next) { 47 | var boldText = '\u001B[1m' + text + '\u001B[22m' 48 | term.erase('screen').position(0, 0).write(boldText) 49 | next() 50 | }).on('end', function () { 51 | term.cursor(true).end() 52 | }) 53 | } 54 | 55 | // render video as ascii characters 56 | module.exports = function (video, options) { 57 | // set the dimensions for the scaled video 58 | var frameWidth = Math.round(options.width) 59 | var frameHeight = Math.round(video.height / (video.width / frameWidth)) 60 | var frameBytes = frameWidth * frameHeight * 3 // rgb24 61 | 62 | ffmpeg 63 | .rawImageStream(video.url, options) 64 | .pipe(new ChopStream(frameBytes)) 65 | .pipe(new ThrottleStream(options.fps)) 66 | .pipe(new AsciiStream(frameWidth, frameHeight, options)) 67 | .pipe(new TerminalStream()) 68 | .resume() // TODO: makes TerminalStream end (should probably be a writable/output stream instead) 69 | } 70 | -------------------------------------------------------------------------------- /lib/ffmpeg.js: -------------------------------------------------------------------------------- 1 | var ffmpeg = require('fluent-ffmpeg') 2 | var debug = require('debug')('yt-term') 3 | 4 | function ffmpegWithEvents (url) { 5 | return ffmpeg(url) 6 | .on('start', function (commandLine) { 7 | debug('Spawned FFmpeg with command: ' + commandLine) 8 | }) 9 | .on('end', function () { 10 | debug('FFmpeg instance ended') 11 | }) 12 | .on('error', function (err) { 13 | console.error('FFmpeg error: ' + err.message) 14 | }) 15 | } 16 | 17 | exports.pcmAudio = function (url) { 18 | return ffmpegWithEvents(url) 19 | .noVideo() 20 | .audioCodec('pcm_s16le') 21 | .format('s16le') 22 | } 23 | 24 | exports.jpegStream = function (url, options) { 25 | return ffmpegWithEvents(url) 26 | .format('image2') 27 | .videoFilters([ 28 | { filter: 'fps', options: options.fps }, 29 | { filter: 'scale', options: options.width + ':-1' } 30 | ]) 31 | .outputOptions('-update', '1') 32 | } 33 | 34 | exports.rawImageStream = function (url, options) { 35 | return ffmpegWithEvents(url) 36 | .format('rawvideo') 37 | .videoFilters([ 38 | { filter: 'fps', options: options.fps }, 39 | { filter: 'scale', options: options.width + ':-1' } 40 | ]) 41 | // .outputOptions('-vcodec', 'rawvideo') 42 | .outputOptions('-pix_fmt', 'rgb24') 43 | .outputOptions('-update', '1') 44 | } 45 | -------------------------------------------------------------------------------- /lib/pcm-audio.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./ffmpeg').pcmAudio 2 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "youtube-terminal", 3 | "version": "0.8.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ansi-styles": { 8 | "version": "3.2.1", 9 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 10 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 11 | "requires": { 12 | "color-convert": "^1.9.0" 13 | } 14 | }, 15 | "ascii-pixels": { 16 | "version": "1.2.0", 17 | "resolved": "https://registry.npmjs.org/ascii-pixels/-/ascii-pixels-1.2.0.tgz", 18 | "integrity": "sha512-VZ6Sih++CrwNiT1EAPIGrYON9iVjwD5dIJvsVult0RhefCSEzutVUZ30dvQpWLcHZxR2NBPfM4i1SIS8EfXJkg==", 19 | "requires": { 20 | "ansi-styles": "^3.2.1" 21 | } 22 | }, 23 | "async": { 24 | "version": "2.5.0", 25 | "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", 26 | "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==", 27 | "requires": { 28 | "lodash": "^4.14.0" 29 | } 30 | }, 31 | "bindings": { 32 | "version": "1.3.0", 33 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", 34 | "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==" 35 | }, 36 | "boolbase": { 37 | "version": "1.0.0", 38 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", 39 | "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" 40 | }, 41 | "buffer-alloc": { 42 | "version": "1.2.0", 43 | "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", 44 | "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", 45 | "requires": { 46 | "buffer-alloc-unsafe": "^1.1.0", 47 | "buffer-fill": "^1.0.0" 48 | } 49 | }, 50 | "buffer-alloc-unsafe": { 51 | "version": "1.1.0", 52 | "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", 53 | "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" 54 | }, 55 | "buffer-fill": { 56 | "version": "1.0.0", 57 | "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", 58 | "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" 59 | }, 60 | "charm": { 61 | "version": "1.0.2", 62 | "resolved": "https://registry.npmjs.org/charm/-/charm-1.0.2.tgz", 63 | "integrity": "sha1-it02cVOm2aWBMxBSxAkJkdqZXjU=", 64 | "requires": { 65 | "inherits": "^2.0.1" 66 | } 67 | }, 68 | "cheerio": { 69 | "version": "0.19.0", 70 | "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz", 71 | "integrity": "sha1-dy5wFfLuKZZQltcepBdbdas1SSU=", 72 | "requires": { 73 | "css-select": "~1.0.0", 74 | "dom-serializer": "~0.1.0", 75 | "entities": "~1.1.1", 76 | "htmlparser2": "~3.8.1", 77 | "lodash": "^3.2.0" 78 | }, 79 | "dependencies": { 80 | "lodash": { 81 | "version": "3.10.1", 82 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", 83 | "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" 84 | } 85 | } 86 | }, 87 | "chop-stream": { 88 | "version": "0.0.2", 89 | "resolved": "https://registry.npmjs.org/chop-stream/-/chop-stream-0.0.2.tgz", 90 | "integrity": "sha512-uX7f/5hlhS2mBz1vx8AAhJJ85AswbMP+fGF+fzPb5G3CJjwb08y0d0B5KVDx+kv5+oII7ga/HruNbOzaxeVgFA==" 91 | }, 92 | "color-convert": { 93 | "version": "1.9.3", 94 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 95 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 96 | "requires": { 97 | "color-name": "1.1.3" 98 | } 99 | }, 100 | "color-name": { 101 | "version": "1.1.3", 102 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 103 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 104 | }, 105 | "core-util-is": { 106 | "version": "1.0.2", 107 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 108 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 109 | }, 110 | "css-select": { 111 | "version": "1.0.0", 112 | "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.0.0.tgz", 113 | "integrity": "sha1-sRIcpRhI3SZOIkTQWM7iVN7rRLA=", 114 | "requires": { 115 | "boolbase": "~1.0.0", 116 | "css-what": "1.0", 117 | "domutils": "1.4", 118 | "nth-check": "~1.0.0" 119 | } 120 | }, 121 | "css-what": { 122 | "version": "1.0.0", 123 | "resolved": "https://registry.npmjs.org/css-what/-/css-what-1.0.0.tgz", 124 | "integrity": "sha1-18wt9FGAZm+Z0rFEYmOUaeAPc2w=" 125 | }, 126 | "debug": { 127 | "version": "4.1.0", 128 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", 129 | "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", 130 | "requires": { 131 | "ms": "^2.1.1" 132 | } 133 | }, 134 | "dom-serializer": { 135 | "version": "0.1.0", 136 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", 137 | "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", 138 | "requires": { 139 | "domelementtype": "~1.1.1", 140 | "entities": "~1.1.1" 141 | }, 142 | "dependencies": { 143 | "domelementtype": { 144 | "version": "1.1.3", 145 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", 146 | "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" 147 | } 148 | } 149 | }, 150 | "domelementtype": { 151 | "version": "1.3.0", 152 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", 153 | "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" 154 | }, 155 | "domhandler": { 156 | "version": "2.3.0", 157 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", 158 | "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", 159 | "requires": { 160 | "domelementtype": "1" 161 | } 162 | }, 163 | "domutils": { 164 | "version": "1.4.3", 165 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz", 166 | "integrity": "sha1-CGVRN5bGswYDGFDhdVFrr4C3Km8=", 167 | "requires": { 168 | "domelementtype": "1" 169 | } 170 | }, 171 | "entities": { 172 | "version": "1.1.1", 173 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", 174 | "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" 175 | }, 176 | "fluent-ffmpeg": { 177 | "version": "2.1.2", 178 | "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz", 179 | "integrity": "sha1-yVLeIkD4EuvaCqgAbXd27irPfXQ=", 180 | "requires": { 181 | "async": ">=0.2.9", 182 | "which": "^1.1.1" 183 | } 184 | }, 185 | "html-entities": { 186 | "version": "1.2.1", 187 | "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", 188 | "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" 189 | }, 190 | "htmlparser2": { 191 | "version": "3.8.3", 192 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", 193 | "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", 194 | "requires": { 195 | "domelementtype": "1", 196 | "domhandler": "2.3", 197 | "domutils": "1.5", 198 | "entities": "1.0", 199 | "readable-stream": "1.1" 200 | }, 201 | "dependencies": { 202 | "domutils": { 203 | "version": "1.5.1", 204 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", 205 | "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", 206 | "requires": { 207 | "dom-serializer": "0", 208 | "domelementtype": "1" 209 | } 210 | }, 211 | "entities": { 212 | "version": "1.0.0", 213 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", 214 | "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=" 215 | }, 216 | "isarray": { 217 | "version": "0.0.1", 218 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 219 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" 220 | }, 221 | "readable-stream": { 222 | "version": "1.1.14", 223 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 224 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 225 | "requires": { 226 | "core-util-is": "~1.0.0", 227 | "inherits": "~2.0.1", 228 | "isarray": "0.0.1", 229 | "string_decoder": "~0.10.x" 230 | } 231 | }, 232 | "string_decoder": { 233 | "version": "0.10.31", 234 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 235 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" 236 | } 237 | } 238 | }, 239 | "inherits": { 240 | "version": "2.0.3", 241 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 242 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 243 | }, 244 | "isarray": { 245 | "version": "1.0.0", 246 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 247 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 248 | }, 249 | "isexe": { 250 | "version": "2.0.0", 251 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 252 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" 253 | }, 254 | "limiter": { 255 | "version": "1.1.3", 256 | "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.3.tgz", 257 | "integrity": "sha512-zrycnIMsLw/3ZxTbW7HCez56rcFGecWTx5OZNplzcXUUmJLmoYArC6qdJzmAN5BWiNXGcpjhF9RQ1HSv5zebEw==" 258 | }, 259 | "lodash": { 260 | "version": "4.17.11", 261 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", 262 | "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" 263 | }, 264 | "m3u8stream": { 265 | "version": "0.6.0", 266 | "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.6.0.tgz", 267 | "integrity": "sha512-h78ollIa7zeq0nzx1+wI/EH0soUpe9A7sC6zw/+/tuRwYdIXedOytvI69+6eP/FU4Ph0cT6kQsw73j5tKXIbgg==", 268 | "requires": { 269 | "miniget": "^1.4.0", 270 | "sax": "^1.2.4" 271 | } 272 | }, 273 | "miniget": { 274 | "version": "1.5.1", 275 | "resolved": "https://registry.npmjs.org/miniget/-/miniget-1.5.1.tgz", 276 | "integrity": "sha512-KJ3AyIVZ76QuWAq43BWjkK+jLdhxhy3s4tsdg9Je91+cIFkeOSW2VEj2lSeKw50CPu1eCCkSbiQEBKL36mpA5w==" 277 | }, 278 | "minimist": { 279 | "version": "1.2.0", 280 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 281 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" 282 | }, 283 | "ms": { 284 | "version": "2.1.1", 285 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 286 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 287 | }, 288 | "nan": { 289 | "version": "2.11.1", 290 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", 291 | "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==" 292 | }, 293 | "nth-check": { 294 | "version": "1.0.1", 295 | "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", 296 | "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", 297 | "requires": { 298 | "boolbase": "~1.0.0" 299 | } 300 | }, 301 | "process-nextick-args": { 302 | "version": "2.0.0", 303 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 304 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" 305 | }, 306 | "readable-stream": { 307 | "version": "2.3.6", 308 | "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 309 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 310 | "requires": { 311 | "core-util-is": "~1.0.0", 312 | "inherits": "~2.0.3", 313 | "isarray": "~1.0.0", 314 | "process-nextick-args": "~2.0.0", 315 | "safe-buffer": "~5.1.1", 316 | "string_decoder": "~1.1.1", 317 | "util-deprecate": "~1.0.1" 318 | } 319 | }, 320 | "safe-buffer": { 321 | "version": "5.1.2", 322 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 323 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 324 | }, 325 | "sax": { 326 | "version": "1.2.4", 327 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 328 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 329 | }, 330 | "speaker": { 331 | "version": "0.4.1", 332 | "resolved": "https://registry.npmjs.org/speaker/-/speaker-0.4.1.tgz", 333 | "integrity": "sha512-n5ef+ti/tRUpo5zUTeQ6zNGVb1i19gNBVbaRcr5s/RzzAILfA+bcJsJSClXCCydSmU4GArUgkFdFUfJTxA2SCA==", 334 | "requires": { 335 | "bindings": "^1.3.0", 336 | "buffer-alloc": "^1.1.0", 337 | "debug": "^3.0.1", 338 | "nan": "^2.6.2", 339 | "readable-stream": "^2.3.3" 340 | }, 341 | "dependencies": { 342 | "debug": { 343 | "version": "3.2.5", 344 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", 345 | "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", 346 | "requires": { 347 | "ms": "^2.1.1" 348 | } 349 | } 350 | } 351 | }, 352 | "string_decoder": { 353 | "version": "1.1.1", 354 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 355 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 356 | "requires": { 357 | "safe-buffer": "~5.1.0" 358 | } 359 | }, 360 | "through2": { 361 | "version": "2.0.5", 362 | "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", 363 | "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", 364 | "requires": { 365 | "readable-stream": "~2.3.6", 366 | "xtend": "~4.0.1" 367 | } 368 | }, 369 | "util-deprecate": { 370 | "version": "1.0.2", 371 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 372 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 373 | }, 374 | "which": { 375 | "version": "1.3.0", 376 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", 377 | "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", 378 | "requires": { 379 | "isexe": "^2.0.0" 380 | } 381 | }, 382 | "xtend": { 383 | "version": "4.0.1", 384 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 385 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" 386 | }, 387 | "youtube-crawler": { 388 | "version": "0.0.3", 389 | "resolved": "https://registry.npmjs.org/youtube-crawler/-/youtube-crawler-0.0.3.tgz", 390 | "integrity": "sha1-5xjl7eMBzDGn5k869gHTtz8fHbY=", 391 | "requires": { 392 | "cheerio": "^0.19.0" 393 | } 394 | }, 395 | "ytdl-core": { 396 | "version": "0.28.0", 397 | "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-0.28.0.tgz", 398 | "integrity": "sha512-pk1nd0MPZLyMOENUOCFLBJ/m59XFqSWPzSY2UhdG7PHBwYtROAY5QRLoYEG0V21h7i3KUeHbnFq8czio5gPKYA==", 399 | "requires": { 400 | "html-entities": "^1.1.3", 401 | "m3u8stream": "^0.6.0", 402 | "miniget": "^1.4.0", 403 | "sax": "^1.1.3" 404 | } 405 | } 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "youtube-terminal", 3 | "version": "0.8.0", 4 | "description": "Stream YouTube videos as ascii art in the terminal!", 5 | "bin": "./index.js", 6 | "scripts": { 7 | "test": "standard && npm outdated" 8 | }, 9 | "keywords": [ 10 | "youtube", 11 | "video", 12 | "ascii", 13 | "terminal", 14 | "player", 15 | "music" 16 | ], 17 | "author": "Mathias Rasmussen ", 18 | "license": "MIT", 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/mathiasvr/youtube-terminal.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/mathiasvr/youtube-terminal/issues" 25 | }, 26 | "homepage": "https://github.com/mathiasvr/youtube-terminal#readme", 27 | "dependencies": { 28 | "ascii-pixels": "^1.2.0", 29 | "charm": "^1.0.1", 30 | "chop-stream": "0.0.2", 31 | "debug": "^4.1.0", 32 | "fluent-ffmpeg": "^2.1.0", 33 | "limiter": "^1.1.3", 34 | "minimist": "^1.2.0", 35 | "speaker": "^0.4.1", 36 | "through2": "^2.0.5", 37 | "youtube-crawler": "0.0.3", 38 | "ytdl-core": "^0.28.0" 39 | } 40 | } 41 | --------------------------------------------------------------------------------