Add ability to import videos from all supported youtube-dl sites

pull/309/head
Chocobozzz 2018-02-20 18:01:38 +01:00
parent 71578f317e
commit 61b3e146e1
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
7 changed files with 104 additions and 51 deletions

View File

@ -1,6 +1,23 @@
# Changelog # Changelog
## v0.0.26-alpha
### BREAKING CHANGES
* Renamed script `import-youtube.js` to `import-videos.js`
* Renamed `import-video.js` argument `youtube-url` to `target-url`
### Features
* Add "Support" attribute/button on videos
* Add ability to import from all [supported sites](https://rg3.github.io/youtube-dl/supportedsites.html) of youtube-dl
### Bug fixes
* Fix custom instance name overflow
## v0.0.25-alpha ## v0.0.25-alpha
### Features ### Features

View File

@ -163,7 +163,7 @@ For now only on Github:
## Tools ## Tools
* [YouTube import](/support/doc/import-youtube.md) * [Import videos (YouTube, Dailymotion, Vimeo...)](/support/doc/import-videos.md)
## Architecture ## Architecture

View File

@ -61,7 +61,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
.switchMap(video => { .switchMap(video => {
return this.videoService return this.videoService
.loadCompleteDescription(video.descriptionPath) .loadCompleteDescription(video.descriptionPath)
.map(description => Object.assign(video, { description })) .map(description => Object.assign(video, { description }))
}) })
.subscribe( .subscribe(
video => { video => {

View File

@ -4,5 +4,5 @@ read -p "This will remove all directories and SQL tables. Are you sure? (y/*) "
echo echo
if [[ "$REPLY" =~ ^[Yy]$ ]]; then if [[ "$REPLY" =~ ^[Yy]$ ]]; then
NODE_ENV=test npm run ts-node "./scripts/danger/clean/cleaner" NODE_ENV=test npm run ts-node -- --type-check "./scripts/danger/clean/cleaner"
fi fi

View File

@ -4,5 +4,5 @@ read -p "This will remove all directories and SQL tables. Are you sure? (y/*) "
echo echo
if [[ "$REPLY" =~ ^[Yy]$ ]]; then if [[ "$REPLY" =~ ^[Yy]$ ]]; then
NODE_ENV=production npm run ts-node "./scripts/danger/clean/cleaner" NODE_ENV=production npm run ts-node -- --type-check "./scripts/danger/clean/cleaner"
fi fi

View File

@ -11,15 +11,16 @@ program
.option('-u, --url <url>', 'Server url') .option('-u, --url <url>', 'Server url')
.option('-U, --username <username>', 'Username') .option('-U, --username <username>', 'Username')
.option('-p, --password <token>', 'Password') .option('-p, --password <token>', 'Password')
.option('-y, --youtube-url <youtubeUrl>', 'Youtube URL') .option('-t, --target-url <targetUrl>', 'Video target URL')
.option('-l, --language <languageCode>', 'Language code') .option('-l, --language <languageCode>', 'Language code')
.option('-v, --verbose', 'Verbose mode')
.parse(process.argv) .parse(process.argv)
if ( if (
!program['url'] || !program['url'] ||
!program['username'] || !program['username'] ||
!program['password'] || !program['password'] ||
!program['youtubeUrl'] !program['targetUrl']
) { ) {
console.error('All arguments are required.') console.error('All arguments are required.')
process.exit(-1) process.exit(-1)
@ -28,6 +29,13 @@ if (
run().catch(err => console.error(err)) run().catch(err => console.error(err))
let accessToken: string let accessToken: string
let client: { id: string, secret: string }
const user = {
username: program['username'],
password: program['password']
}
const processOptions = { const processOptions = {
cwd: __dirname, cwd: __dirname,
maxBuffer: Infinity maxBuffer: Infinity
@ -35,74 +43,72 @@ const processOptions = {
async function run () { async function run () {
const res = await getClient(program['url']) const res = await getClient(program['url'])
const client = { client = {
id: res.body.client_id, id: res.body.client_id,
secret: res.body.client_secret secret: res.body.client_secret
} }
const user = {
username: program['username'],
password: program['password']
}
const res2 = await login(program['url'], client, user) const res2 = await login(program['url'], client, user)
accessToken = res2.body.access_token accessToken = res2.body.access_token
const options = [ '-j', '--flat-playlist', '--playlist-reverse' ] const options = [ '-j', '--flat-playlist', '--playlist-reverse' ]
youtubeDL.getInfo(program['youtubeUrl'], options, processOptions, async (err, info) => { youtubeDL.getInfo(program['targetUrl'], options, processOptions, async (err, info) => {
if (err) throw err if (err) throw err
let infoArray: any[]
// Normalize utf8 fields // Normalize utf8 fields
info = info.map(i => normalizeObject(i)) if (Array.isArray(info) === true) {
infoArray = info.map(i => normalizeObject(i))
} else {
infoArray = [ normalizeObject(info) ]
}
console.log('Will download and upload %d videos.\n', infoArray.length)
const videos = info.map(i => { for (const info of infoArray) {
return { url: 'https://www.youtube.com/watch?v=' + i.id, name: i.title } await processVideo(info, program['language'])
})
console.log('Will download and upload %d videos.\n', videos.length)
for (const video of videos) {
await processVideo(video, program['language'], client, user)
} }
console.log('I have finished!') // https://www.youtube.com/watch?v=2Upx39TBc1s
console.log('I\'m finished!')
process.exit(0) process.exit(0)
}) })
} }
function processVideo (video: { name: string, url: string }, languageCode: number, client: { id: string, secret: string }, user: { username: string, password: string }) { function processVideo (info: any, languageCode: number) {
return new Promise(async res => { return new Promise(async res => {
const result = await searchVideo(program['url'], video.name) if (program['verbose']) console.log('Fetching object.', info)
const videoInfo = await fetchObject(info)
if (program['verbose']) console.log('Fetched object.', videoInfo)
const result = await searchVideo(program['url'], videoInfo.title)
console.log('############################################################\n') console.log('############################################################\n')
if (result.body.data.find(v => v.name === video.name)) { if (result.body.data.find(v => v.name === videoInfo.title)) {
console.log('Video "%s" already exists, don\'t reupload it.\n', video.name) console.log('Video "%s" already exists, don\'t reupload it.\n', videoInfo.title)
return res() return res()
} }
const path = join(__dirname, new Date().getTime() + '.mp4') const path = join(__dirname, new Date().getTime() + '.mp4')
console.log('Downloading video "%s"...', video.name) console.log('Downloading video "%s"...', videoInfo.title)
const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]', '-o', path ] const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ]
youtubeDL.exec(video.url, options, processOptions, async (err, output) => { youtubeDL.exec(videoInfo.url, options, processOptions, async (err, output) => {
if (err) return console.error(err) if (err) return console.error(err)
console.log(output.join('\n')) console.log(output.join('\n'))
youtubeDL.getInfo(video.url, undefined, processOptions, async (err, videoInfo) => { await uploadVideoOnPeerTube(normalizeObject(videoInfo), path, languageCode)
if (err) return console.error(err)
await uploadVideoOnPeerTube(normalizeObject(videoInfo), path, client, user, languageCode) return res()
return res()
})
}) })
}) })
} }
async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, client: { id: string, secret: string }, user: { username: string, password: string }, language?: number) { async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, language?: number) {
const category = await getCategory(videoInfo.categories) const category = await getCategory(videoInfo.categories)
const licence = getLicence(videoInfo.license) const licence = getLicence(videoInfo.license)
let tags = [] let tags = []
@ -141,13 +147,16 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, client:
console.log('\nUploading on PeerTube video "%s".', videoAttributes.name) console.log('\nUploading on PeerTube video "%s".', videoAttributes.name)
try { try {
await uploadVideo(program['url'], accessToken, videoAttributes) await uploadVideo(program['url'], accessToken, videoAttributes)
} } catch (err) {
catch (err) { if (err.message.indexOf('401')) {
if ((err.message).search("401")) { console.log('Got 401 Unauthorized, token may have expired, renewing token and retry.')
console.log("Get 401 Unauthorized, token may have expired, renewing token and retry.")
const res2 = await login(program['url'], client, user) const res = await login(program['url'], client, user)
accessToken = res2.body.access_token accessToken = res.body.access_token
await uploadVideo(program['url'], accessToken, videoAttributes) await uploadVideo(program['url'], accessToken, videoAttributes)
} else {
throw err
} }
} }
@ -160,6 +169,8 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, client:
} }
async function getCategory (categories: string[]) { async function getCategory (categories: string[]) {
if (!categories) return undefined
const categoryString = categories[0] const categoryString = categories[0]
if (categoryString === 'News & Politics') return 11 if (categoryString === 'News & Politics') return 11
@ -176,6 +187,8 @@ async function getCategory (categories: string[]) {
} }
function getLicence (licence: string) { function getLicence (licence: string) {
if (!licence) return undefined
if (licence.indexOf('Creative Commons Attribution licence') !== -1) return 1 if (licence.indexOf('Creative Commons Attribution licence') !== -1) return 1
return undefined return undefined
@ -199,3 +212,24 @@ function normalizeObject (obj: any) {
return newObj return newObj
} }
function fetchObject (info: any) {
const url = buildUrl(info)
return new Promise<any>(async (res, rej) => {
youtubeDL.getInfo(url, undefined, processOptions, async (err, videoInfo) => {
if (err) return rej(err)
const videoInfoWithUrl = Object.assign(videoInfo, { url })
return res(normalizeObject(videoInfoWithUrl))
})
})
}
function buildUrl (info: any) {
const url = info.url as string
if (url && url.match(/^https?:\/\//)) return info.url
// It seems youtube-dl does not return the video url
return 'https://www.youtube.com/watch?v=' + info.id
}

View File

@ -1,6 +1,6 @@
# Import videos from Youtube guide # Import videos guide
You can use this script to import videos from Youtube channel to Peertube. You can use this script to import videos from all [supported sites of youtube-dl](https://rg3.github.io/youtube-dl/supportedsites.html) into PeerTube.
Be sure you own the videos or have the author's authorization to do so. Be sure you own the videos or have the author's authorization to do so.
- [Installation](#installation) - [Installation](#installation)
@ -16,7 +16,6 @@ Importation can be launched directly from a PeerTube server (in this case you al
### Dependencies ### Dependencies
* [PeerTube dependencies](dependencies.md) * [PeerTube dependencies](dependencies.md)
* git
### Installation ### Installation
@ -46,16 +45,19 @@ You are now ready to run the script :
``` ```
cd ${CLONE} cd ${CLONE}
node dist/server/tools/import-youtube.js -u "PEERTUBE_URL" -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD" -y "YOUTUBE_URL" node dist/server/tools/import-video.js -u "PEERTUBE_URL" -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD" -t "TARGET_URL"
``` ```
* PEERTUBE_URL : the full URL of your PeerTube server where you want to import, eg: https://peertube.cpy.re/ * PEERTUBE_URL : the full URL of your PeerTube server where you want to import, eg: https://peertube.cpy.re/
* PEERTUBE_USER : your PeerTube account where videos will be uploaded * PEERTUBE_USER : your PeerTube account where videos will be uploaded
* PEERTUBE_PASSWORD : password of your PeerTube account * PEERTUBE_PASSWORD : password of your PeerTube account
* YOUTUBE_URL : the youtube video/user/channel/playlist you want to import. Examples: * TARGET_URL : the target url you want to import. Examples:
* Channel: https://www.youtube.com/channel/ChannelId * YouTube:
* User https://www.youtube.com/c/UserName or https://www.youtube.com/user/UserName * Channel: https://www.youtube.com/channel/ChannelId
* Video https://www.youtube.com/watch?v=blabla * User https://www.youtube.com/c/UserName or https://www.youtube.com/user/UserName
* Video https://www.youtube.com/watch?v=blabla
* Vimeo: https://vimeo.com/xxxxxx
* Dailymotion: https://www.dailymotion.com/xxxxx
The script will get all public videos from Youtube, download them and upload to PeerTube. The script will get all public videos from Youtube, download them and upload to PeerTube.
Already downloaded videos will not be uploaded twice, so you can run and re-run the script in case of crash, disconnection... Already downloaded videos will not be uploaded twice, so you can run and re-run the script in case of crash, disconnection...