-
Notifications
You must be signed in to change notification settings - Fork 12
/
Coub.js
123 lines (92 loc) · 3.39 KB
/
Coub.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
const FFmkek = require('ffmkek')
const TempFile = require('./TempFile')
const { fetchWithRetry } = require('./Util')
const { sep } = require('path')
const { Readable } = require('stream')
class Coub extends FFmkek {
constructor(metadata) {
super()
while (metadata.type === 'Coub::Recoub') metadata = metadata.recoub_to
this.metadata = metadata
this.duration = metadata.duration
this.urls = metadata.file_versions.html5
;[this.width, this.height] = metadata.dimensions['big']
this.videoURL = this.videoInput = this.urls.video.high.url || this.urls.video.med.url
if (this.urls.audio) {
this.audioURL = this.audioInput = this.urls.audio.high.url || this.urls.audio.med.url
}
this.videoPart = this.currentPart
this.audioPart = null
this.addInput(this.videoInput)
}
get url() {
return `https://coub.com/view/${this.metadata.permalink}`
}
crop(crop) {
if (typeof crop !== 'string') {
const offset = this.width / 2 - this.height / 2
crop = `${this.height}:${this.height}:${offset}:0`
}
this.addOption('-vf', `crop=${crop}`)
return this
}
scale(params) {
params = params.toString()
const separator = params.includes('x') ? 'x' : ':'
const [width, height] = params.split(separator)
this.width = width
if (height) this.height = height
const filterString = [width, height]
.map(num => (isNaN(num) ? '-2' : num))
.join(separator)
this.addOption('-vf', `scale=${filterString}`)
return this
}
attachAudio() {
if (!this.audioInput) return this
this.audioPart = this.currentPart
return this.addInput(this.audioInput)
}
loop(times) {
if (times < 2) return this
const path = this.videoInput.split(sep).join('/').replace(' ', '\\ ')
const list = new TempFile(`file ${path}\n`.repeat(times), 'txt').writeSync()
this.videoPart
.setName(list.path)
.addOption('-f', 'concat')
.addOption('-safe', '0')
return this
}
downloadSources() {
const promises = [this.downloadVideo()]
if (this.audioPart) promises.push(this.downloadAudio())
return Promise.all(promises)
}
async downloadVideo() {
if (!this.videoInput.startsWith('http')) return
const videoStream = await fetchWithRetry(this.videoURL).then(response => Readable.from(response.body))
videoStream.once('data', buffer => (buffer[0] = buffer[1] = 0)) // Decode weird Coub encoding.
const video = await new TempFile(videoStream, 'mp4').write()
this.videoInput = this.videoPart.name = video.path
}
async downloadAudio() {
const audioStream = await fetchWithRetry(this.audioURL).then(response => Readable.from(response.body))
const audio = await new TempFile(audioStream, 'mp3').write()
this.audioInput = this.audioPart.name = audio.path
}
static async fetch(url) {
const id = url.split('/').slice(-1)[0]
const response = await fetchWithRetry(`http://coub.com/api/v2/coubs/${id}`)
// Check if coub was moved
if (response.status == 404) {
const webURL = `https://coub.com/view/${id}`
const webResponse = await fetchWithRetry(webURL)
if (webResponse.url !== webURL) return Coub.fetch(webResponse.url)
}
const metadata = await response.json()
if (!response.ok)
throw new Error(metadata.error || 'Encountered and error while fetching the Coub')
return new Coub(metadata)
}
}
module.exports = Coub