diff options
author | Chocobozzz <me@florianbigard.com> | 2018-02-09 16:47:06 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-02-09 16:47:06 +0100 |
commit | a7fea183f0f69104b209e7bfdd6435be28165f22 (patch) | |
tree | a0bfb7daf8e9778c664b8d7224b2e1c382e3834e /server/tools | |
parent | 1185c246c591a0783dc0ab268bbd67eba1d46bb9 (diff) | |
download | PeerTube-a7fea183f0f69104b209e7bfdd6435be28165f22.tar.gz PeerTube-a7fea183f0f69104b209e7bfdd6435be28165f22.tar.zst PeerTube-a7fea183f0f69104b209e7bfdd6435be28165f22.zip |
Begin import script with youtube-dl
Diffstat (limited to 'server/tools')
-rw-r--r-- | server/tools/get-access-token.ts | 48 | ||||
-rw-r--r-- | server/tools/import-youtube.ts | 155 | ||||
-rw-r--r-- | server/tools/upload-directory.ts | 82 | ||||
-rw-r--r-- | server/tools/upload.ts | 85 |
4 files changed, 370 insertions, 0 deletions
diff --git a/server/tools/get-access-token.ts b/server/tools/get-access-token.ts new file mode 100644 index 000000000..66fa70814 --- /dev/null +++ b/server/tools/get-access-token.ts | |||
@@ -0,0 +1,48 @@ | |||
1 | import * as program from 'commander' | ||
2 | |||
3 | import { | ||
4 | getClient, | ||
5 | serverLogin | ||
6 | } from '../tests/utils/index' | ||
7 | |||
8 | program | ||
9 | .option('-u, --url <url>', 'Server url') | ||
10 | .option('-n, --username <username>', 'Username') | ||
11 | .option('-p, --password <token>', 'Password') | ||
12 | .parse(process.argv) | ||
13 | |||
14 | if ( | ||
15 | !program['url'] || | ||
16 | !program['username'] || | ||
17 | !program['password'] | ||
18 | ) { | ||
19 | throw new Error('All arguments are required.') | ||
20 | } | ||
21 | |||
22 | const server = { | ||
23 | url: program['url'], | ||
24 | user: { | ||
25 | username: program['username'], | ||
26 | password: program['password'] | ||
27 | }, | ||
28 | client: { | ||
29 | id: null, | ||
30 | secret: null | ||
31 | } | ||
32 | } | ||
33 | |||
34 | getClient(program.url) | ||
35 | .then(res => { | ||
36 | server.client.id = res.body.client_id | ||
37 | server.client.secret = res.body.client_secret | ||
38 | |||
39 | return serverLogin(server) | ||
40 | }) | ||
41 | .then(accessToken => { | ||
42 | console.log(accessToken) | ||
43 | process.exit(0) | ||
44 | }) | ||
45 | .catch(err => { | ||
46 | console.error(err) | ||
47 | process.exit(-1) | ||
48 | }) | ||
diff --git a/server/tools/import-youtube.ts b/server/tools/import-youtube.ts new file mode 100644 index 000000000..b4405c452 --- /dev/null +++ b/server/tools/import-youtube.ts | |||
@@ -0,0 +1,155 @@ | |||
1 | import * as program from 'commander' | ||
2 | import { createWriteStream } from 'fs' | ||
3 | import { join } from 'path' | ||
4 | import { cursorTo } from 'readline' | ||
5 | import * as youtubeDL from 'youtube-dl' | ||
6 | import { VideoPrivacy } from '../../shared/models/videos' | ||
7 | import { unlinkPromise } from '../helpers/core-utils' | ||
8 | import { getClient, getVideoCategories, login, searchVideo, uploadVideo } from '../tests/utils' | ||
9 | |||
10 | program | ||
11 | .option('-u, --url <url>', 'Server url') | ||
12 | .option('-U, --username <username>', 'Username') | ||
13 | .option('-p, --password <token>', 'Password') | ||
14 | .option('-y, --youtube-url <directory>', 'Youtube URL') | ||
15 | .parse(process.argv) | ||
16 | |||
17 | if ( | ||
18 | !program['url'] || | ||
19 | !program['username'] || | ||
20 | !program['password'] || | ||
21 | !program['youtubeUrl'] | ||
22 | ) { | ||
23 | throw new Error('All arguments are required.') | ||
24 | } | ||
25 | |||
26 | run().catch(err => console.error(err)) | ||
27 | |||
28 | let accessToken: string | ||
29 | |||
30 | async function run () { | ||
31 | const res = await getClient(program['url']) | ||
32 | const client = { | ||
33 | id: res.body.client_id, | ||
34 | secret: res.body.client_secret | ||
35 | } | ||
36 | |||
37 | const user = { | ||
38 | username: program['username'], | ||
39 | password: program['password'] | ||
40 | } | ||
41 | |||
42 | const res2 = await login(program['url'], client, user) | ||
43 | accessToken = res2.body.access_token | ||
44 | |||
45 | youtubeDL.getInfo(program['youtubeUrl'], [ '-j', '--flat-playlist' ], async (err, info) => { | ||
46 | if (err) throw err | ||
47 | |||
48 | const videos = info.map(i => { | ||
49 | return { url: 'https://www.youtube.com/watch?v=' + i.id, name: i.title } | ||
50 | }) | ||
51 | |||
52 | console.log('Will download and upload %d videos.\n', videos.length) | ||
53 | |||
54 | for (const video of videos) { | ||
55 | await processVideo(video) | ||
56 | } | ||
57 | |||
58 | console.log('I\'m finished!') | ||
59 | process.exit(0) | ||
60 | }) | ||
61 | } | ||
62 | |||
63 | function processVideo (videoUrlName: { name: string, url: string }) { | ||
64 | return new Promise(async res => { | ||
65 | const result = await searchVideo(program['url'], videoUrlName.name) | ||
66 | if (result.body.total !== 0) { | ||
67 | console.log('Video "%s" already exist, don\'t reupload it.\n', videoUrlName.name) | ||
68 | return res() | ||
69 | } | ||
70 | |||
71 | const video = youtubeDL(videoUrlName.url) | ||
72 | let videoInfo | ||
73 | let videoPath: string | ||
74 | |||
75 | video.on('error', err => console.error(err)) | ||
76 | |||
77 | let size = 0 | ||
78 | video.on('info', info => { | ||
79 | videoInfo = info | ||
80 | size = info.size | ||
81 | |||
82 | videoPath = join(__dirname, size + '.mp4') | ||
83 | console.log('Creating "%s" of video "%s".', videoPath, videoInfo.title) | ||
84 | |||
85 | video.pipe(createWriteStream(videoPath)) | ||
86 | }) | ||
87 | |||
88 | let pos = 0 | ||
89 | video.on('data', chunk => { | ||
90 | pos += chunk.length | ||
91 | // `size` should not be 0 here. | ||
92 | if (size) { | ||
93 | const percent = (pos / size * 100).toFixed(2) | ||
94 | writeWaitingPercent(percent) | ||
95 | } | ||
96 | }) | ||
97 | |||
98 | video.on('end', async () => { | ||
99 | await uploadVideoOnPeerTube(videoInfo, videoPath) | ||
100 | |||
101 | return res() | ||
102 | }) | ||
103 | }) | ||
104 | } | ||
105 | |||
106 | function writeWaitingPercent (p: string) { | ||
107 | cursorTo(process.stdout, 0) | ||
108 | process.stdout.write(`waiting ... ${p}%`) | ||
109 | } | ||
110 | |||
111 | async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string) { | ||
112 | const category = await getCategory(videoInfo.categories) | ||
113 | const licence = getLicence(videoInfo.license) | ||
114 | const language = 13 | ||
115 | |||
116 | const videoAttributes = { | ||
117 | name: videoInfo.title, | ||
118 | category, | ||
119 | licence, | ||
120 | language, | ||
121 | nsfw: false, | ||
122 | commentsEnabled: true, | ||
123 | description: videoInfo.description, | ||
124 | tags: videoInfo.tags.slice(0, 5), | ||
125 | privacy: VideoPrivacy.PUBLIC, | ||
126 | fixture: videoPath | ||
127 | } | ||
128 | |||
129 | console.log('\nUploading on PeerTube video "%s".', videoAttributes.name) | ||
130 | await uploadVideo(program['url'], accessToken, videoAttributes) | ||
131 | await unlinkPromise(videoPath) | ||
132 | console.log('Uploaded video "%s"!\n', videoAttributes.name) | ||
133 | } | ||
134 | |||
135 | async function getCategory (categories: string[]) { | ||
136 | const categoryString = categories[0] | ||
137 | |||
138 | if (categoryString === 'News & Politics') return 11 | ||
139 | |||
140 | const res = await getVideoCategories(program['url']) | ||
141 | const categoriesServer = res.body | ||
142 | |||
143 | for (const key of Object.keys(categoriesServer)) { | ||
144 | const categoryServer = categoriesServer[key] | ||
145 | if (categoryString.toLowerCase() === categoryServer.toLowerCase()) return parseInt(key, 10) | ||
146 | } | ||
147 | |||
148 | return undefined | ||
149 | } | ||
150 | |||
151 | function getLicence (licence: string) { | ||
152 | if (licence.indexOf('Creative Commons Attribution licence') !== -1) return 1 | ||
153 | |||
154 | return undefined | ||
155 | } | ||
diff --git a/server/tools/upload-directory.ts b/server/tools/upload-directory.ts new file mode 100644 index 000000000..c0094f852 --- /dev/null +++ b/server/tools/upload-directory.ts | |||
@@ -0,0 +1,82 @@ | |||
1 | import * as program from 'commander' | ||
2 | import * as Promise from 'bluebird' | ||
3 | import { isAbsolute, join } from 'path' | ||
4 | |||
5 | import { readdirPromise } from '../helpers/core-utils' | ||
6 | import { execCLI } from '../tests/utils/index' | ||
7 | |||
8 | program | ||
9 | .option('-u, --url <url>', 'Server url') | ||
10 | .option('-U, --username <username>', 'Username') | ||
11 | .option('-p, --password <token>', 'Password') | ||
12 | .option('-i, --input <directory>', 'Videos directory absolute path') | ||
13 | .option('-d, --description <description>', 'Video descriptions') | ||
14 | .option('-c, --category <category>', 'Video categories') | ||
15 | .option('-l, --licence <licence>', 'Video licences') | ||
16 | .option('-t, --tags <tags>', 'Video tags', list) | ||
17 | .parse(process.argv) | ||
18 | |||
19 | if ( | ||
20 | !program['url'] || | ||
21 | !program['username'] || | ||
22 | !program['password'] || | ||
23 | !program['input'] || | ||
24 | !program['description'] || | ||
25 | !program['category'] || | ||
26 | !program['licence'] || | ||
27 | !program['tags'] | ||
28 | ) { | ||
29 | throw new Error('All arguments are required.') | ||
30 | } | ||
31 | |||
32 | if (isAbsolute(program['input']) === false) { | ||
33 | throw new Error('Input path should be absolute.') | ||
34 | } | ||
35 | |||
36 | let command = `npm run ts-node -- ${__dirname}/get-access-token.ts` | ||
37 | command += ` -u "${program['url']}"` | ||
38 | command += ` -n "${program['username']}"` | ||
39 | command += ` -p "${program['password']}"` | ||
40 | |||
41 | execCLI(command) | ||
42 | .then(stdout => { | ||
43 | const accessToken = stdout.replace('\n', '') | ||
44 | |||
45 | console.log(accessToken) | ||
46 | |||
47 | return readdirPromise(program['input']).then(files => ({ accessToken, files })) | ||
48 | }) | ||
49 | .then(({ accessToken, files }) => { | ||
50 | return Promise.each(files, file => { | ||
51 | const video = { | ||
52 | tags: program['tags'], | ||
53 | name: file, | ||
54 | description: program['description'], | ||
55 | category: program['category'], | ||
56 | licence: program['licence'] | ||
57 | } | ||
58 | |||
59 | let command = `npm run ts-node -- ${__dirname}/upload.ts` | ||
60 | command += ` -u "${program['url']}"` | ||
61 | command += ` -a "${accessToken}"` | ||
62 | command += ` -n "${video.name}"` | ||
63 | command += ` -d "${video.description}"` | ||
64 | command += ` -c "${video.category}"` | ||
65 | command += ` -l "${video.licence}"` | ||
66 | command += ` -t "${video.tags.join(',')}"` | ||
67 | command += ` -f "${join(program['input'], file)}"` | ||
68 | |||
69 | return execCLI(command).then(stdout => console.log(stdout)) | ||
70 | }) | ||
71 | }) | ||
72 | .then(() => process.exit(0)) | ||
73 | .catch(err => { | ||
74 | console.error(err) | ||
75 | process.exit(-1) | ||
76 | }) | ||
77 | |||
78 | // ---------------------------------------------------------------------------- | ||
79 | |||
80 | function list (val) { | ||
81 | return val.split(',') | ||
82 | } | ||
diff --git a/server/tools/upload.ts b/server/tools/upload.ts new file mode 100644 index 000000000..db59bbdff --- /dev/null +++ b/server/tools/upload.ts | |||
@@ -0,0 +1,85 @@ | |||
1 | import * as program from 'commander' | ||
2 | import { access, constants } from 'fs' | ||
3 | import { isAbsolute } from 'path' | ||
4 | import { promisify } from 'util' | ||
5 | |||
6 | const accessPromise = promisify(access) | ||
7 | |||
8 | import { uploadVideo } from '../tests/utils/index' | ||
9 | |||
10 | program | ||
11 | .option('-u, --url <url>', 'Server url') | ||
12 | .option('-a, --access-token <token>', 'Access token') | ||
13 | .option('-n, --name <name>', 'Video name') | ||
14 | .option('-N, --nsfw', 'Video is Not Safe For Work') | ||
15 | .option('-c, --category <category number>', 'Category number') | ||
16 | .option('-l, --licence <licence number>', 'Licence number') | ||
17 | .option('-L, --language <language number>', 'Language number') | ||
18 | .option('-d, --description <description>', 'Video description') | ||
19 | .option('-t, --tags <tags>', 'Video tags', list) | ||
20 | .option('-f, --file <file>', 'Video absolute file path') | ||
21 | .parse(process.argv) | ||
22 | |||
23 | if (!program['tags']) program['tags'] = [] | ||
24 | if (!program['nsfw']) program['nsfw'] = false | ||
25 | |||
26 | if ( | ||
27 | !program['url'] || | ||
28 | !program['accessToken'] || | ||
29 | !program['name'] || | ||
30 | !program['category'] || | ||
31 | !program['licence'] || | ||
32 | !program['description'] || | ||
33 | !program['file'] | ||
34 | ) { | ||
35 | throw new Error('All arguments but tags, language and nsfw are required.') | ||
36 | } | ||
37 | |||
38 | if (isAbsolute(program['file']) === false) { | ||
39 | throw new Error('File path should be absolute.') | ||
40 | } | ||
41 | |||
42 | accessPromise(program['file'], constants.F_OK) | ||
43 | .then(() => { | ||
44 | return upload( | ||
45 | program['url'], | ||
46 | program['accessToken'], | ||
47 | program['name'], | ||
48 | program['category'], | ||
49 | program['licence'], | ||
50 | program['language'], | ||
51 | program['nsfw'], | ||
52 | program['description'], | ||
53 | program['tags'], | ||
54 | program['file'] | ||
55 | ) | ||
56 | }) | ||
57 | .then(() => process.exit(0)) | ||
58 | .catch(err => { | ||
59 | console.error(err) | ||
60 | process.exit(-1) | ||
61 | }) | ||
62 | |||
63 | // ---------------------------------------------------------------------------- | ||
64 | |||
65 | function list (val) { | ||
66 | return val.split(',') | ||
67 | } | ||
68 | |||
69 | function upload (url, accessToken, name, category, licence, language, nsfw, description, tags, fixture) { | ||
70 | console.log('Uploading %s video...', program['name']) | ||
71 | |||
72 | const videoAttributes = { | ||
73 | name, | ||
74 | category, | ||
75 | licence, | ||
76 | language, | ||
77 | nsfw, | ||
78 | description, | ||
79 | tags, | ||
80 | fixture | ||
81 | } | ||
82 | return uploadVideo(url, accessToken, videoAttributes).then(() => { | ||
83 | console.log(`Video ${name} uploaded.`) | ||
84 | }) | ||
85 | } | ||