aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRigel Kent <sendmemail@rigelk.eu>2018-09-13 14:27:44 +0200
committerRigel Kent <sendmemail@rigelk.eu>2018-09-14 11:08:55 +0200
commit8704acf49efc770d73bf07c10468ed8c74d28a83 (patch)
treeffd46289fcf9a13ac4412b167e9f71dfb35753c5
parent1d9d9cfdcf3983e3fd89026bc4b5633a8abf5752 (diff)
downloadPeerTube-8704acf49efc770d73bf07c10468ed8c74d28a83.tar.gz
PeerTube-8704acf49efc770d73bf07c10468ed8c74d28a83.tar.zst
PeerTube-8704acf49efc770d73bf07c10468ed8c74d28a83.zip
one cli to unite them all
Ash nazg thrakatulûk agh burzum-ishi krimpatul - refactor import-videos to use the youtubeDL helper - add very basic tests for the cli
-rw-r--r--package.json10
-rw-r--r--server/helpers/youtube-dl.ts23
-rw-r--r--server/tests/cli/index.ts1
-rw-r--r--server/tests/cli/peertube.ts51
-rw-r--r--server/tools/cli.ts63
-rw-r--r--server/tools/peertube-auth.ts140
-rw-r--r--server/tools/peertube-get-access-token.ts (renamed from server/tools/get-access-token.ts)5
-rw-r--r--server/tools/peertube-import-videos.ts (renamed from server/tools/import-videos.ts)109
-rw-r--r--server/tools/peertube-upload.ts (renamed from server/tools/upload.ts)74
-rw-r--r--server/tools/peertube-watch.ts61
-rwxr-xr-xserver/tools/peertube.ts81
-rw-r--r--support/doc/tools.md75
-rw-r--r--tsconfig.json1
13 files changed, 616 insertions, 78 deletions
diff --git a/package.json b/package.json
index 5a8843b0c..cc4f6be5c 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,9 @@
7 "engines": { 7 "engines": {
8 "node": ">=8.x" 8 "node": ">=8.x"
9 }, 9 },
10 "bin": {
11 "peertube": "dist/server/tools/peertube.js"
12 },
10 "author": { 13 "author": {
11 "name": "Florian Bigard", 14 "name": "Florian Bigard",
12 "email": "florian.bigard@gmail.com", 15 "email": "florian.bigard@gmail.com",
@@ -78,6 +81,7 @@
78 "@types/bluebird": "3.5.21" 81 "@types/bluebird": "3.5.21"
79 }, 82 },
80 "dependencies": { 83 "dependencies": {
84 "application-config": "^1.0.1",
81 "async": "^2.0.0", 85 "async": "^2.0.0",
82 "async-lock": "^1.1.2", 86 "async-lock": "^1.1.2",
83 "async-lru": "^1.1.1", 87 "async-lru": "^1.1.1",
@@ -86,6 +90,7 @@
86 "bluebird": "^3.5.0", 90 "bluebird": "^3.5.0",
87 "body-parser": "^1.12.4", 91 "body-parser": "^1.12.4",
88 "bull": "^3.4.2", 92 "bull": "^3.4.2",
93 "cli-table": "^0.3.1",
89 "bytes": "^3.0.0", 94 "bytes": "^3.0.0",
90 "commander": "^2.13.0", 95 "commander": "^2.13.0",
91 "concurrently": "^4.0.1", 96 "concurrently": "^4.0.1",
@@ -113,6 +118,7 @@
113 "magnet-uri": "^5.1.4", 118 "magnet-uri": "^5.1.4",
114 "morgan": "^1.5.3", 119 "morgan": "^1.5.3",
115 "multer": "^1.1.0", 120 "multer": "^1.1.0",
121 "netrc-parser": "^3.1.6",
116 "nodemailer": "^4.4.2", 122 "nodemailer": "^4.4.2",
117 "parse-torrent": "^6.0.0", 123 "parse-torrent": "^6.0.0",
118 "password-generator": "^2.0.2", 124 "password-generator": "^2.0.2",
@@ -130,6 +136,7 @@
130 "sequelize-typescript": "0.6.6", 136 "sequelize-typescript": "0.6.6",
131 "sharp": "^0.20.0", 137 "sharp": "^0.20.0",
132 "srt-to-vtt": "^1.1.2", 138 "srt-to-vtt": "^1.1.2",
139 "summon-install": "^0.4.3",
133 "useragent": "^2.3.0", 140 "useragent": "^2.3.0",
134 "uuid": "^3.1.0", 141 "uuid": "^3.1.0",
135 "validator": "^10.2.0", 142 "validator": "^10.2.0",
@@ -196,5 +203,8 @@
196 "scripty": { 203 "scripty": {
197 "silent": true 204 "silent": true
198 }, 205 },
206 "summon": {
207 "silent": true
208 },
199 "sasslintConfig": "client/.sass-lint.yml" 209 "sasslintConfig": "client/.sass-lint.yml"
200} 210}
diff --git a/server/helpers/youtube-dl.ts b/server/helpers/youtube-dl.ts
index 6738090f3..8b2bc1782 100644
--- a/server/helpers/youtube-dl.ts
+++ b/server/helpers/youtube-dl.ts
@@ -14,9 +14,9 @@ export type YoutubeDLInfo = {
14 thumbnailUrl?: string 14 thumbnailUrl?: string
15} 15}
16 16
17function getYoutubeDLInfo (url: string): Promise<YoutubeDLInfo> { 17function getYoutubeDLInfo (url: string, opts?: string[]): Promise<YoutubeDLInfo> {
18 return new Promise<YoutubeDLInfo>(async (res, rej) => { 18 return new Promise<YoutubeDLInfo>(async (res, rej) => {
19 const options = [ '-j', '--flat-playlist' ] 19 const options = opts || [ '-j', '--flat-playlist' ]
20 20
21 const youtubeDL = await safeGetYoutubeDL() 21 const youtubeDL = await safeGetYoutubeDL()
22 youtubeDL.getInfo(url, options, (err, info) => { 22 youtubeDL.getInfo(url, options, (err, info) => {
@@ -48,15 +48,6 @@ function downloadYoutubeDLVideo (url: string) {
48 }) 48 })
49} 49}
50 50
51// ---------------------------------------------------------------------------
52
53export {
54 downloadYoutubeDLVideo,
55 getYoutubeDLInfo
56}
57
58// ---------------------------------------------------------------------------
59
60async function safeGetYoutubeDL () { 51async function safeGetYoutubeDL () {
61 let youtubeDL 52 let youtubeDL
62 53
@@ -71,6 +62,16 @@ async function safeGetYoutubeDL () {
71 return youtubeDL 62 return youtubeDL
72} 63}
73 64
65// ---------------------------------------------------------------------------
66
67export {
68 downloadYoutubeDLVideo,
69 getYoutubeDLInfo,
70 safeGetYoutubeDL
71}
72
73// ---------------------------------------------------------------------------
74
74function normalizeObject (obj: any) { 75function normalizeObject (obj: any) {
75 const newObj: any = {} 76 const newObj: any = {}
76 77
diff --git a/server/tests/cli/index.ts b/server/tests/cli/index.ts
index f99eafe03..33e33a070 100644
--- a/server/tests/cli/index.ts
+++ b/server/tests/cli/index.ts
@@ -1,5 +1,6 @@
1// Order of the tests we want to execute 1// Order of the tests we want to execute
2import './create-transcoding-job' 2import './create-transcoding-job'
3import './create-import-video-file-job' 3import './create-import-video-file-job'
4import './peertube'
4import './reset-password' 5import './reset-password'
5import './update-host' 6import './update-host'
diff --git a/server/tests/cli/peertube.ts b/server/tests/cli/peertube.ts
new file mode 100644
index 000000000..548fd1257
--- /dev/null
+++ b/server/tests/cli/peertube.ts
@@ -0,0 +1,51 @@
1import 'mocha'
2import {
3 expect
4} from 'chai'
5import {
6 createUser,
7 execCLI,
8 flushTests,
9 getEnvCli,
10 killallServers,
11 runServer,
12 ServerInfo,
13 setAccessTokensToServers
14} from '../utils'
15
16describe('Test CLI wrapper', function () {
17 let server: ServerInfo
18 const cmd = 'node ./dist/server/tools/peertube.js'
19
20 before(async function () {
21 this.timeout(30000)
22
23 await flushTests()
24 server = await runServer(1)
25 await setAccessTokensToServers([ server ])
26
27 await createUser(server.url, server.accessToken, 'user_1', 'super password')
28 })
29
30 it('Should display no selected instance', async function () {
31 this.timeout(60000)
32
33 const env = getEnvCli(server)
34 const stdout = await execCLI(`${env} ${cmd} --help`)
35
36 expect(stdout).to.contain('selected')
37 })
38
39 it('Should remember the authentifying material of the user', async function () {
40 this.timeout(60000)
41
42 const env = getEnvCli(server)
43 const stdout = await execCLI(`${env} ` + cmd + ` auth add --url ${server.url} -U user_1 -p "super password"`)
44 })
45
46 after(async function () {
47 await execCLI(cmd + ` auth del ${server.url}`)
48
49 killallServers([ server ])
50 })
51})
diff --git a/server/tools/cli.ts b/server/tools/cli.ts
new file mode 100644
index 000000000..9a170d4da
--- /dev/null
+++ b/server/tools/cli.ts
@@ -0,0 +1,63 @@
1const config = require('application-config')('PeerTube/CLI')
2const netrc = require('netrc-parser').default
3
4const version = () => {
5 const tag = require('child_process')
6 .execSync('[[ ! -d .git ]] || git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || true', { stdio: [0,1,2] })
7 if (tag) return tag
8
9 const version = require('child_process')
10 .execSync('[[ ! -d .git ]] || git rev-parse --short HEAD').toString().trim()
11 if (version) return version
12
13 return require('../../../package.json').version
14}
15
16let settings = {
17 remotes: [],
18 default: 0
19}
20
21interface Settings {
22 remotes: any[],
23 default: number
24}
25
26async function getSettings () {
27 return new Promise<Settings>((res, rej) => {
28 let settings = {
29 remotes: [],
30 default: 0
31 } as Settings
32 config.read((err, data) => {
33 if (err) {
34 return rej(err)
35 }
36 return res(data || settings)
37 })
38 })
39}
40
41async function writeSettings (settings) {
42 return new Promise((res, rej) => {
43 config.write(settings, function (err) {
44 if (err) {
45 return rej(err)
46 }
47 return res()
48 })
49 })
50}
51
52netrc.loadSync()
53
54// ---------------------------------------------------------------------------
55
56export {
57 version,
58 config,
59 settings,
60 getSettings,
61 writeSettings,
62 netrc
63}
diff --git a/server/tools/peertube-auth.ts b/server/tools/peertube-auth.ts
new file mode 100644
index 000000000..33438811e
--- /dev/null
+++ b/server/tools/peertube-auth.ts
@@ -0,0 +1,140 @@
1import * as program from 'commander'
2import * as prompt from 'prompt'
3const Table = require('cli-table')
4import { getSettings, writeSettings, netrc } from './cli'
5import { isHostValid } from '../helpers/custom-validators/servers'
6import { isUserUsernameValid } from '../helpers/custom-validators/users'
7
8function delInstance (url: string) {
9 return new Promise((res, rej): void => {
10 getSettings()
11 .then(async (settings) => {
12 settings.remotes.splice(settings.remotes.indexOf(url))
13 await writeSettings(settings)
14 delete netrc.machines[url]
15 netrc.save()
16 res()
17 })
18 .catch(err => rej(err))
19 })
20}
21
22async function setInstance (url: string, username: string, password: string) {
23 return new Promise((res, rej): void => {
24 getSettings()
25 .then(async settings => {
26 if (settings.remotes.indexOf(url) === -1) {
27 settings.remotes.push(url)
28 }
29 await writeSettings(settings)
30 netrc.machines[url] = { login: username, password }
31 netrc.save()
32 res()
33 })
34 .catch(err => rej(err))
35 })
36}
37
38function isURLaPeerTubeInstance (url: string) {
39 return isHostValid(url) || (url.includes('localhost'))
40}
41
42program
43 .name('auth')
44 .usage('[command] [options]')
45
46program
47 .command('add')
48 .description('remember your accounts on remote instances for easier use')
49 .option('-u, --url <url>', 'Server url')
50 .option('-U, --username <username>', 'Username')
51 .option('-p, --password <token>', 'Password')
52 .option('--default', 'add the entry as the new default')
53 .action(options => {
54 prompt.override = options
55 prompt.start()
56 prompt.get({
57 properties: {
58 url: {
59 description: 'instance url',
60 conform: (value) => isURLaPeerTubeInstance(value),
61 required: true
62 },
63 username: {
64 conform: (value) => isUserUsernameValid(value),
65 message: 'Name must be only letters, spaces, or dashes',
66 required: true
67 },
68 password: {
69 hidden: true,
70 replace: '*',
71 required: true
72 }
73 }
74 }, (_, result) => {
75 setInstance(result.url, result.username, result.password)
76 })
77 })
78
79program
80 .command('del <url>')
81 .description('unregisters a remote instance')
82 .action((url) => {
83 delInstance(url)
84 })
85
86program
87 .command('list')
88 .description('lists registered remote instances')
89 .action(() => {
90 getSettings()
91 .then(settings => {
92 const table = new Table({
93 head: ['instance', 'login'],
94 colWidths: [30, 30]
95 })
96 netrc.loadSync()
97 settings.remotes.forEach(element => {
98 table.push([
99 element,
100 netrc.machines[element].login
101 ])
102 })
103
104 console.log(table.toString())
105 })
106 })
107
108program
109 .command('set-default <url>')
110 .description('set an existing entry as default')
111 .action((url) => {
112 getSettings()
113 .then(settings => {
114 const instanceExists = settings.remotes.indexOf(url) !== -1
115
116 if (instanceExists) {
117 settings.default = settings.remotes.indexOf(url)
118 writeSettings(settings)
119 } else {
120 console.log('<url> is not a registered instance.')
121 process.exit(-1)
122 }
123 })
124 })
125
126program.on('--help', function () {
127 console.log(' Examples:')
128 console.log()
129 console.log(' $ peertube add -u peertube.cpy.re -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"')
130 console.log(' $ peertube add -u peertube.cpy.re -U root')
131 console.log(' $ peertube list')
132 console.log(' $ peertube del peertube.cpy.re')
133 console.log()
134})
135
136if (!process.argv.slice(2).length) {
137 program.outputHelp()
138}
139
140program.parse(process.argv)
diff --git a/server/tools/get-access-token.ts b/server/tools/peertube-get-access-token.ts
index d86c84c8d..eb2571a03 100644
--- a/server/tools/get-access-token.ts
+++ b/server/tools/peertube-get-access-token.ts
@@ -19,7 +19,10 @@ if (
19 !program['username'] || 19 !program['username'] ||
20 !program['password'] 20 !program['password']
21) { 21) {
22 throw new Error('All arguments are required.') 22 if (!program['url']) console.error('--url field is required.')
23 if (!program['username']) console.error('--username field is required.')
24 if (!program['password']) console.error('--password field is required.')
25 process.exit(-1)
23} 26}
24 27
25getClient(program.url) 28getClient(program.url)
diff --git a/server/tools/import-videos.ts b/server/tools/peertube-import-videos.ts
index 3ff194c83..13090a028 100644
--- a/server/tools/import-videos.ts
+++ b/server/tools/peertube-import-videos.ts
@@ -3,7 +3,6 @@ require('tls').DEFAULT_ECDH_CURVE = 'auto'
3 3
4import * as program from 'commander' 4import * as program from 'commander'
5import { join } from 'path' 5import { join } from 'path'
6import * as youtubeDL from 'youtube-dl'
7import { VideoPrivacy } from '../../shared/models/videos' 6import { VideoPrivacy } from '../../shared/models/videos'
8import { doRequestAndSaveToFile } from '../helpers/requests' 7import { doRequestAndSaveToFile } from '../helpers/requests'
9import { CONSTRAINTS_FIELDS } from '../initializers' 8import { CONSTRAINTS_FIELDS } from '../initializers'
@@ -11,8 +10,19 @@ import { getClient, getVideoCategories, login, searchVideoWithSort, uploadVideo
11import { truncate } from 'lodash' 10import { truncate } from 'lodash'
12import * as prompt from 'prompt' 11import * as prompt from 'prompt'
13import { remove } from 'fs-extra' 12import { remove } from 'fs-extra'
13import { safeGetYoutubeDL } from '../helpers/youtube-dl'
14import { getSettings, netrc } from './cli'
15
16let accessToken: string
17let client: { id: string, secret: string }
18
19const processOptions = {
20 cwd: __dirname,
21 maxBuffer: Infinity
22}
14 23
15program 24program
25 .name('import-videos')
16 .option('-u, --url <url>', 'Server url') 26 .option('-u, --url <url>', 'Server url')
17 .option('-U, --username <username>', 'Username') 27 .option('-U, --username <username>', 'Username')
18 .option('-p, --password <token>', 'Password') 28 .option('-p, --password <token>', 'Password')
@@ -21,29 +31,50 @@ program
21 .option('-v, --verbose', 'Verbose mode') 31 .option('-v, --verbose', 'Verbose mode')
22 .parse(process.argv) 32 .parse(process.argv)
23 33
24if ( 34getSettings()
25 !program['url'] || 35.then(settings => {
26 !program['username'] || 36 if (
27 !program['targetUrl'] 37 (!program['url'] ||
28) { 38 !program['username'] ||
29 console.error('All arguments are required.') 39 !program['password']) &&
30 process.exit(-1) 40 (settings.remotes.length === 0)
31} 41 ) {
42 if (!program['url']) console.error('--url field is required.')
43 if (!program['username']) console.error('--username field is required.')
44 if (!program['password']) console.error('--password field is required.')
45 if (!program['targetUrl']) console.error('--targetUrl field is required.')
46 process.exit(-1)
47 }
32 48
33const user = { 49 if (
34 username: program['username'], 50 (!program['url'] ||
35 password: program['password'] 51 !program['username'] ||
36} 52 !program['password']) &&
53 (settings.remotes.length > 0)
54 ) {
55 if (!program['url']) {
56 program['url'] = (settings.default !== -1) ?
57 settings.remotes[settings.default] :
58 settings.remotes[0]
59 }
60 if (!program['username']) program['username'] = netrc.machines[program['url']].login
61 if (!program['password']) program['password'] = netrc.machines[program['url']].password
62 }
37 63
38run().catch(err => console.error(err)) 64 if (
65 !program['targetUrl']
66 ) {
67 if (!program['targetUrl']) console.error('--targetUrl field is required.')
68 process.exit(-1)
69 }
39 70
40let accessToken: string 71 const user = {
41let client: { id: string, secret: string } 72 username: program['username'],
73 password: program['password']
74 }
42 75
43const processOptions = { 76 run(user, program['url']).catch(err => console.error(err))
44 cwd: __dirname, 77})
45 maxBuffer: Infinity
46}
47 78
48async function promptPassword () { 79async function promptPassword () {
49 return new Promise((res, rej) => { 80 return new Promise((res, rej) => {
@@ -65,20 +96,22 @@ async function promptPassword () {
65 }) 96 })
66} 97}
67 98
68async function run () { 99async function run (user, url: string) {
69 if (!user.password) { 100 if (!user.password) {
70 user.password = await promptPassword() 101 user.password = await promptPassword()
71 } 102 }
72 103
73 const res = await getClient(program['url']) 104 const res = await getClient(url)
74 client = { 105 client = {
75 id: res.body.client_id, 106 id: res.body.client_id,
76 secret: res.body.client_secret 107 secret: res.body.client_secret
77 } 108 }
78 109
79 const res2 = await login(program['url'], client, user) 110 const res2 = await login(url, client, user)
80 accessToken = res2.body.access_token 111 accessToken = res2.body.access_token
81 112
113 const youtubeDL = await safeGetYoutubeDL()
114
82 const options = [ '-j', '--flat-playlist', '--playlist-reverse' ] 115 const options = [ '-j', '--flat-playlist', '--playlist-reverse' ]
83 youtubeDL.getInfo(program['targetUrl'], options, processOptions, async (err, info) => { 116 youtubeDL.getInfo(program['targetUrl'], options, processOptions, async (err, info) => {
84 if (err) { 117 if (err) {
@@ -97,7 +130,7 @@ async function run () {
97 console.log('Will download and upload %d videos.\n', infoArray.length) 130 console.log('Will download and upload %d videos.\n', infoArray.length)
98 131
99 for (const info of infoArray) { 132 for (const info of infoArray) {
100 await processVideo(info, program['language']) 133 await processVideo(info, program['language'], processOptions.cwd, url, user)
101 } 134 }
102 135
103 // https://www.youtube.com/watch?v=2Upx39TBc1s 136 // https://www.youtube.com/watch?v=2Upx39TBc1s
@@ -106,14 +139,14 @@ async function run () {
106 }) 139 })
107} 140}
108 141
109function processVideo (info: any, languageCode: string) { 142function processVideo (info: any, languageCode: string, cwd: string, url: string, user) {
110 return new Promise(async res => { 143 return new Promise(async res => {
111 if (program['verbose']) console.log('Fetching object.', info) 144 if (program['verbose']) console.log('Fetching object.', info)
112 145
113 const videoInfo = await fetchObject(info) 146 const videoInfo = await fetchObject(info)
114 if (program['verbose']) console.log('Fetched object.', videoInfo) 147 if (program['verbose']) console.log('Fetched object.', videoInfo)
115 148
116 const result = await searchVideoWithSort(program['url'], videoInfo.title, '-match') 149 const result = await searchVideoWithSort(url, videoInfo.title, '-match')
117 150
118 console.log('############################################################\n') 151 console.log('############################################################\n')
119 152
@@ -122,12 +155,13 @@ function processVideo (info: any, languageCode: string) {
122 return res() 155 return res()
123 } 156 }
124 157
125 const path = join(__dirname, new Date().getTime() + '.mp4') 158 const path = join(cwd, new Date().getTime() + '.mp4')
126 159
127 console.log('Downloading video "%s"...', videoInfo.title) 160 console.log('Downloading video "%s"...', videoInfo.title)
128 161
129 const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ] 162 const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ]
130 try { 163 try {
164 const youtubeDL = await safeGetYoutubeDL()
131 youtubeDL.exec(videoInfo.url, options, processOptions, async (err, output) => { 165 youtubeDL.exec(videoInfo.url, options, processOptions, async (err, output) => {
132 if (err) { 166 if (err) {
133 console.error(err) 167 console.error(err)
@@ -135,7 +169,7 @@ function processVideo (info: any, languageCode: string) {
135 } 169 }
136 170
137 console.log(output.join('\n')) 171 console.log(output.join('\n'))
138 await uploadVideoOnPeerTube(normalizeObject(videoInfo), path, languageCode) 172 await uploadVideoOnPeerTube(normalizeObject(videoInfo), path, cwd, url, user, languageCode)
139 return res() 173 return res()
140 }) 174 })
141 } catch (err) { 175 } catch (err) {
@@ -145,8 +179,8 @@ function processVideo (info: any, languageCode: string) {
145 }) 179 })
146} 180}
147 181
148async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, language?: string) { 182async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, cwd: string, url: string, user, language?: string) {
149 const category = await getCategory(videoInfo.categories) 183 const category = await getCategory(videoInfo.categories, url)
150 const licence = getLicence(videoInfo.license) 184 const licence = getLicence(videoInfo.license)
151 let tags = [] 185 let tags = []
152 if (Array.isArray(videoInfo.tags)) { 186 if (Array.isArray(videoInfo.tags)) {
@@ -158,7 +192,7 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, languag
158 192
159 let thumbnailfile 193 let thumbnailfile
160 if (videoInfo.thumbnail) { 194 if (videoInfo.thumbnail) {
161 thumbnailfile = join(__dirname, 'thumbnail.jpg') 195 thumbnailfile = join(cwd, 'thumbnail.jpg')
162 196
163 await doRequestAndSaveToFile({ 197 await doRequestAndSaveToFile({
164 method: 'GET', 198 method: 'GET',
@@ -189,15 +223,15 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, languag
189 223
190 console.log('\nUploading on PeerTube video "%s".', videoAttributes.name) 224 console.log('\nUploading on PeerTube video "%s".', videoAttributes.name)
191 try { 225 try {
192 await uploadVideo(program['url'], accessToken, videoAttributes) 226 await uploadVideo(url, accessToken, videoAttributes)
193 } catch (err) { 227 } catch (err) {
194 if (err.message.indexOf('401') !== -1) { 228 if (err.message.indexOf('401') !== -1) {
195 console.log('Got 401 Unauthorized, token may have expired, renewing token and retry.') 229 console.log('Got 401 Unauthorized, token may have expired, renewing token and retry.')
196 230
197 const res = await login(program['url'], client, user) 231 const res = await login(url, client, user)
198 accessToken = res.body.access_token 232 accessToken = res.body.access_token
199 233
200 await uploadVideo(program['url'], accessToken, videoAttributes) 234 await uploadVideo(url, accessToken, videoAttributes)
201 } else { 235 } else {
202 console.log(err.message) 236 console.log(err.message)
203 process.exit(1) 237 process.exit(1)
@@ -210,14 +244,14 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, languag
210 console.log('Uploaded video "%s"!\n', videoAttributes.name) 244 console.log('Uploaded video "%s"!\n', videoAttributes.name)
211} 245}
212 246
213async function getCategory (categories: string[]) { 247async function getCategory (categories: string[], url: string) {
214 if (!categories) return undefined 248 if (!categories) return undefined
215 249
216 const categoryString = categories[0] 250 const categoryString = categories[0]
217 251
218 if (categoryString === 'News & Politics') return 11 252 if (categoryString === 'News & Politics') return 11
219 253
220 const res = await getVideoCategories(program['url']) 254 const res = await getVideoCategories(url)
221 const categoriesServer = res.body 255 const categoriesServer = res.body
222 256
223 for (const key of Object.keys(categoriesServer)) { 257 for (const key of Object.keys(categoriesServer)) {
@@ -228,6 +262,8 @@ async function getCategory (categories: string[]) {
228 return undefined 262 return undefined
229} 263}
230 264
265/* ---------------------------------------------------------- */
266
231function getLicence (licence: string) { 267function getLicence (licence: string) {
232 if (!licence) return undefined 268 if (!licence) return undefined
233 269
@@ -259,6 +295,7 @@ function fetchObject (info: any) {
259 const url = buildUrl(info) 295 const url = buildUrl(info)
260 296
261 return new Promise<any>(async (res, rej) => { 297 return new Promise<any>(async (res, rej) => {
298 const youtubeDL = await safeGetYoutubeDL()
262 youtubeDL.getInfo(url, undefined, processOptions, async (err, videoInfo) => { 299 youtubeDL.getInfo(url, undefined, processOptions, async (err, videoInfo) => {
263 if (err) return rej(err) 300 if (err) return rej(err)
264 301
diff --git a/server/tools/upload.ts b/server/tools/peertube-upload.ts
index 9b104d308..1f871e660 100644
--- a/server/tools/upload.ts
+++ b/server/tools/peertube-upload.ts
@@ -4,18 +4,20 @@ import { isAbsolute } from 'path'
4import { getClient, login } from '../tests/utils' 4import { getClient, login } from '../tests/utils'
5import { uploadVideo } from '../tests/utils/index' 5import { uploadVideo } from '../tests/utils/index'
6import { VideoPrivacy } from '../../shared/models/videos' 6import { VideoPrivacy } from '../../shared/models/videos'
7import { netrc, getSettings } from './cli'
7 8
8program 9program
10 .name('upload')
9 .option('-u, --url <url>', 'Server url') 11 .option('-u, --url <url>', 'Server url')
10 .option('-U, --username <username>', 'Username') 12 .option('-U, --username <username>', 'Username')
11 .option('-p, --password <token>', 'Password') 13 .option('-p, --password <token>', 'Password')
12 .option('-n, --video-name <name>', 'Video name') 14 .option('-n, --video-name <name>', 'Video name')
13 .option('-P, --privacy <privacy number>', 'Privacy') 15 .option('-P, --privacy <privacy_number>', 'Privacy')
14 .option('-N, --nsfw', 'Video is Not Safe For Work') 16 .option('-N, --nsfw', 'Video is Not Safe For Work')
15 .option('-c, --category <category number>', 'Category number') 17 .option('-c, --category <category_number>', 'Category number')
16 .option('-m, --comments-enabled', 'Enable comments') 18 .option('-m, --comments-enabled', 'Enable comments')
17 .option('-l, --licence <licence number>', 'Licence number') 19 .option('-l, --licence <licence_number>', 'Licence number')
18 .option('-L, --language <language code>', 'Language ISO 639 code (fr or en...)') 20 .option('-L, --language <language_code>', 'Language ISO 639 code (fr or en...)')
19 .option('-d, --video-description <description>', 'Video description') 21 .option('-d, --video-description <description>', 'Video description')
20 .option('-t, --tags <tags>', 'Video tags', list) 22 .option('-t, --tags <tags>', 'Video tags', list)
21 .option('-b, --thumbnail <thumbnailPath>', 'Thumbnail path') 23 .option('-b, --thumbnail <thumbnailPath>', 'Thumbnail path')
@@ -28,27 +30,53 @@ if (!program['nsfw']) program['nsfw'] = false
28if (!program['privacy']) program['privacy'] = VideoPrivacy.PUBLIC 30if (!program['privacy']) program['privacy'] = VideoPrivacy.PUBLIC
29if (!program['commentsEnabled']) program['commentsEnabled'] = false 31if (!program['commentsEnabled']) program['commentsEnabled'] = false
30 32
31if ( 33getSettings()
32 !program['url'] || 34 .then(settings => {
33 !program['username'] || 35 if (
34 !program['password'] || 36 (!program['url'] ||
35 !program['videoName'] || 37 !program['username'] ||
36 !program['file'] 38 !program['password']) &&
37) { 39 (settings.remotes.length === 0)
38 if (!program['url']) console.error('--url field is required.') 40 ) {
39 if (!program['username']) console.error('--username field is required.') 41 if (!program['url']) console.error('--url field is required.')
40 if (!program['password']) console.error('--password field is required.') 42 if (!program['username']) console.error('--username field is required.')
41 if (!program['videoName']) console.error('--video-name field is required.') 43 if (!program['password']) console.error('--password field is required.')
42 if (!program['file']) console.error('--file field is required.') 44 if (!program['videoName']) console.error('--video-name field is required.')
43 process.exit(-1) 45 if (!program['file']) console.error('--file field is required.')
44} 46 process.exit(-1)
47 }
45 48
46if (isAbsolute(program['file']) === false) { 49 if (
47 console.error('File path should be absolute.') 50 (!program['url'] ||
48 process.exit(-1) 51 !program['username'] ||
49} 52 !program['password']) &&
53 (settings.remotes.length > 0)
54 ) {
55 if (!program['url']) {
56 program['url'] = (settings.default !== -1) ?
57 settings.remotes[settings.default] :
58 settings.remotes[0]
59 }
60 if (!program['username']) program['username'] = netrc.machines[program['url']].login
61 if (!program['password']) program['password'] = netrc.machines[program['url']].password
62 }
63
64 if (
65 !program['videoName'] ||
66 !program['file']
67 ) {
68 if (!program['videoName']) console.error('--video-name field is required.')
69 if (!program['file']) console.error('--file field is required.')
70 process.exit(-1)
71 }
72
73 if (isAbsolute(program['file']) === false) {
74 console.error('File path should be absolute.')
75 process.exit(-1)
76 }
50 77
51run().catch(err => console.error(err)) 78 run().catch(err => console.error(err))
79 })
52 80
53async function run () { 81async function run () {
54 const res = await getClient(program[ 'url' ]) 82 const res = await getClient(program[ 'url' ])
diff --git a/server/tools/peertube-watch.ts b/server/tools/peertube-watch.ts
new file mode 100644
index 000000000..bf7274aab
--- /dev/null
+++ b/server/tools/peertube-watch.ts
@@ -0,0 +1,61 @@
1import * as program from 'commander'
2import * as summon from 'summon-install'
3import { join } from 'path'
4import { execSync } from 'child_process'
5import { root } from '../helpers/core-utils'
6
7let videoURL
8
9program
10 .name('watch')
11 .arguments('<url>')
12 .option('-g, --gui <player>', 'player type', /^(airplay|stdout|chromecast|mpv|vlc|mplayer|ascii|xbmc)$/i, 'ascii')
13 .option('-i, --invert', 'invert colors (ascii player only)', true)
14 .option('-r, --resolution <res>', 'video resolution', /^(240|360|720|1080)$/i, '720')
15 .on('--help', function () {
16 console.log(' Available Players:')
17 console.log()
18 console.log(' - ascii')
19 console.log(' - mpv')
20 console.log(' - mplayer')
21 console.log(' - vlc')
22 console.log(' - stdout')
23 console.log(' - xbmc')
24 console.log(' - airplay')
25 console.log(' - chromecast')
26 console.log()
27 console.log(' Note: \'ascii\' is the only option not using WebTorrent and not seeding back the video.')
28 console.log()
29 console.log(' Examples:')
30 console.log()
31 console.log(' $ peertube watch -g mpv https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10')
32 console.log(' $ peertube watch --gui stdout https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10')
33 console.log(' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10')
34 console.log()
35 })
36 .action((url) => {
37 videoURL = url
38 })
39 .parse(process.argv)
40
41if (!videoURL) {
42 console.error('<url> positional argument is required.')
43 process.exit(-1)
44} else { program['url'] = videoURL }
45
46handler(program)
47
48function handler (argv) {
49 if (argv['gui'] === 'ascii') {
50 summon('peerterminal')
51 const peerterminal = summon('peerterminal')
52 peerterminal([ '--link', videoURL, '--invert', argv['invert'] ])
53 } else {
54 summon('webtorrent-hybrid')
55 const CMD = 'node ' + join(root(), 'node_modules', 'webtorrent-hybrid', 'bin', 'cmd.js')
56 const CMDargs = ` --${argv.gui} ` +
57 argv['url'].replace('videos/watch', 'download/torrents') +
58 `-${argv.resolution}.torrent`
59 execSync(CMD + CMDargs)
60 }
61}
diff --git a/server/tools/peertube.ts b/server/tools/peertube.ts
new file mode 100755
index 000000000..7441161b1
--- /dev/null
+++ b/server/tools/peertube.ts
@@ -0,0 +1,81 @@
1#!/usr/bin/env node
2
3import * as program from 'commander'
4import {
5 version,
6 getSettings
7} from './cli'
8
9program
10 .version(version(), '-v, --version')
11 .usage('[command] [options]')
12
13/* Subcommands automatically loaded in the directory and beginning by peertube-* */
14program
15 .command('auth [action]', 'register your accounts on remote instances to use them with other commands')
16 .command('upload', 'upload a video').alias('up')
17 .command('import-videos', 'import a video from a streaming platform').alias('import')
18 .command('get-access-token', 'get a peertube access token', { noHelp: true }).alias('token')
19 .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w')
20
21/* Not Yet Implemented */
22program
23 .command('plugins [action]',
24 'manage plugins on a local instance',
25 { noHelp: true } as program.CommandOptions
26 ).alias('p')
27 .command('diagnostic [action]',
28 'like couple therapy, but for your instance',
29 { noHelp: true } as program.CommandOptions
30 ).alias('d')
31 .command('admin',
32 'manage an instance where you have elevated rights',
33 { noHelp: true } as program.CommandOptions
34 ).alias('a')
35
36// help on no command
37if (!process.argv.slice(2).length) {
38 const logo = '░P░e░e░r░T░u░b░e░'
39 console.log(`
40 ___/),.._ ` + logo + `
41/' ,. ."'._
42( "' '-.__"-._ ,-
43\\'='='), "\\ -._-"-. -"/
44 / ""/"\\,_\\,__"" _" /,-
45 / / -" _/"/
46 / | ._\\\\ |\\ |_.".-" /
47 / | __\\)|)|),/|_." _,."
48 / \_." " ") | ).-""---''--
49 ( "/.""7__-""''
50 | " ."._--._
51 \\ \\ (_ __ "" ".,_
52 \\.,. \\ "" -"".-"
53 ".,_, (",_-,,,-".-
54 "'-,\\_ __,-"
55 ",)" ")
56 /"\\-"
57 ,"\\/
58 _,.__/"\\/_ (the CLI for red chocobos)
59 / \\) "./, ".
60 --/---"---" "-) )---- by Chocobozzz et al.`)
61}
62
63getSettings()
64 .then(settings => {
65 const state = (settings.default === -1) ?
66 'no instance selected, commands will require explicit arguments' :
67 ('instance ' + settings.remotes[settings.default] + ' selected')
68 program
69 .on('--help', function () {
70 console.log()
71 console.log(' State: ' + state)
72 console.log()
73 console.log(' Examples:')
74 console.log()
75 console.log(' $ peertube auth add -u "PEERTUBE_URL" -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"')
76 console.log(' $ peertube up <videoFile>')
77 console.log(' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10')
78 console.log()
79 })
80 .parse(process.argv)
81 })
diff --git a/support/doc/tools.md b/support/doc/tools.md
index 0a2f1f11b..1db29edc0 100644
--- a/support/doc/tools.md
+++ b/support/doc/tools.md
@@ -1,14 +1,60 @@
1# CLI tools guide 1# CLI tools guide
2 2 - [CLI wrapper](#cli-wrapper)
3 - [Remote tools](#remote-tools) 3 - [Remote tools](#remote-tools)
4 - [import-videos.js](#import-videosjs) 4 - [peertube-import-videos.js](#peertube-import-videosjs)
5 - [upload.js](#uploadjs) 5 - [peertube-upload.js](#peertube-uploadjs)
6 - [peertube-watch.js](#peertube-watch)
6 - [Server tools](#server-tools) 7 - [Server tools](#server-tools)
7 - [parse-log](#parse-log) 8 - [parse-log](#parse-log)
8 - [create-transcoding-job.js](#create-transcoding-jobjs) 9 - [create-transcoding-job.js](#create-transcoding-jobjs)
9 - [create-import-video-file-job.js](#create-import-video-file-jobjs) 10 - [create-import-video-file-job.js](#create-import-video-file-jobjs)
10 - [prune-storage.js](#prune-storagejs) 11 - [prune-storage.js](#prune-storagejs)
11 12
13## CLI wrapper
14
15The wrapper provides a convenient interface to most scripts, and requires the [same dependencies](#dependencies). You can access it as `peertube` via an alias in your `.bashrc` like `alias peertube="node ${PEERTUBE_PATH}/dist/server/tools/peertube.js"`:
16
17```
18 Usage: peertube [command] [options]
19
20 Options:
21
22 -v, --version output the version number
23 -h, --help output usage information
24
25 Commands:
26
27 auth [action] register your accounts on remote instances to use them with other commands
28 upload|up upload a video
29 import-videos|import import a video from a streaming platform
30 watch|w watch a video in the terminal ✩°。⋆
31 help [cmd] display help for [cmd]
32```
33
34The wrapper can keep track of instances you have an account on. We limit to one account per instance for now.
35
36```bash
37$ peertube auth add -u "PEERTUBE_URL" -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"
38$ peertube auth list
39┌──────────────────────────────┬──────────────────────────────┐
40│ instance │ login │
41├──────────────────────────────┼──────────────────────────────┤
42│ "PEERTUBE_URL" │ "PEERTUBE_USER" │
43└──────────────────────────────┴──────────────────────────────┘
44```
45
46You can now use that account to upload videos without feeding the same parameters again.
47
48```bash
49$ peertube up <videoFile>
50```
51
52And now that your video is online, you can watch it from the confort of your terminal (use `peertube watch --help` to see the supported players):
53
54```bash
55$ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10
56```
57
12## Remote Tools 58## Remote Tools
13 59
14You need at least 512MB RAM to run the script. 60You need at least 512MB RAM to run the script.
@@ -40,13 +86,13 @@ $ cd ${CLONE}
40$ npm run build:server 86$ npm run build:server
41``` 87```
42 88
43### import-videos.js 89### peertube-import-videos.js
44 90
45You can use this script to import videos from all [supported sites of youtube-dl](https://rg3.github.io/youtube-dl/supportedsites.html) into PeerTube. 91You can use this script to import videos from all [supported sites of youtube-dl](https://rg3.github.io/youtube-dl/supportedsites.html) into PeerTube.
46Be sure you own the videos or have the author's authorization to do so. 92Be sure you own the videos or have the author's authorization to do so.
47 93
48```sh 94```sh
49$ node dist/server/tools/import-videos.js \ 95$ node dist/server/tools/peertube-import-videos.js \
50 -u "PEERTUBE_URL" \ 96 -u "PEERTUBE_URL" \
51 -U "PEERTUBE_USER" \ 97 -U "PEERTUBE_USER" \
52 --password "PEERTUBE_PASSWORD" \ 98 --password "PEERTUBE_PASSWORD" \
@@ -70,7 +116,7 @@ Already downloaded videos will not be uploaded twice, so you can run and re-run
70Videos will be publicly available after transcoding (you can see them before that in your account on the web interface). 116Videos will be publicly available after transcoding (you can see them before that in your account on the web interface).
71 117
72 118
73### upload.js 119### peertube-upload.js
74 120
75You can use this script to import videos directly from the CLI. 121You can use this script to import videos directly from the CLI.
76 122
@@ -78,9 +124,24 @@ Videos will be publicly available after transcoding (you can see them before tha
78 124
79``` 125```
80$ cd ${CLONE} 126$ cd ${CLONE}
81$ node dist/server/tools/upload.js --help 127$ node dist/server/tools/peertube-upload.js --help
82``` 128```
83 129
130### peertube-watch.js
131
132You can use this script to play videos directly from the CLI.
133
134It provides support for different players:
135
136- ascii (default ; plays in ascii art in your terminal!)
137- mpv
138- mplayer
139- vlc
140- stdout
141- xbmc
142- airplay
143- chromecast
144
84 145
85## Server tools 146## Server tools
86 147
diff --git a/tsconfig.json b/tsconfig.json
index 7633465b2..c84b179cf 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -6,6 +6,7 @@
6 "sourceMap": false, 6 "sourceMap": false,
7 "experimentalDecorators": true, 7 "experimentalDecorators": true,
8 "emitDecoratorMetadata": true, 8 "emitDecoratorMetadata": true,
9 "removeComments": true,
9 "outDir": "./dist", 10 "outDir": "./dist",
10 "lib": [ 11 "lib": [
11 "dom", 12 "dom",