Skip to content

Commit 6e82d70

Browse files
committed
feat: download quality
1 parent 719f045 commit 6e82d70

File tree

6 files changed

+102
-6
lines changed

6 files changed

+102
-6
lines changed

client/index.html

+16
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,22 @@ <h2>Settings </h2>
6868
<div><small>Show the creators chosen thumbnail, or a neutral one based on the actual video frames</small></div>
6969
</form>
7070
<br>
71+
<form>
72+
<div class="flex space-between">
73+
<div>
74+
Video quality
75+
</div>
76+
<select name="video-quality" id="video-quality">
77+
<option value="360">360</option>
78+
<option value="480">480</option>
79+
<option value="720">720</option>
80+
<option value="1080">1080</option>
81+
<option value="1440">1440</option>
82+
<option value="2160">2160</option>
83+
</select>
84+
</div>
85+
</form>
86+
<br>
7187
<form>
7288
<div class="flex space-between">
7389
<div>

client/lib/router.js

+23
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,29 @@ const routes = {
6969
$showOriginalThumbnail.addEventListener('click', (event) => {
7070
store.toggle(store.showOriginalThumbnailKey)
7171
})
72+
73+
const $videoQuality = document.getElementById('video-quality')
74+
$videoQuality.addEventListener('change', (event) => {
75+
console.log('change video quality', event.target.value)
76+
window.fetch('/api/video-quality', {
77+
method: 'POST',
78+
headers: {
79+
'Content-Type': 'application/json'
80+
},
81+
body: event.target.value
82+
})
83+
})
84+
window.fetch('/api/video-quality', {
85+
method: 'GET'
86+
}).then(response => response.json())
87+
.then((videoQuality) => {
88+
console.log('video quality', videoQuality)
89+
$videoQuality.value = +videoQuality
90+
})
91+
.catch(error => console.error('Error:', error))
92+
93+
94+
7295

7396
const $reclaimDiskSpace = document.getElementById('reclaim-disk-space')
7497
const $diskUsage = document.getElementById('disk-usage')

lib/repository.js

+23
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export default class Repository {
88
this.paths = {
99
videos: `${this.basePath}/videos.json`,
1010
channels: `${this.basePath}/channels.json`,
11+
settings: `${this.basePath}/settings.json`
1112
}
1213

1314
fs.mkdirSync(`${this.basePath}/videos`, { recursive: true })
@@ -19,12 +20,18 @@ export default class Repository {
1920
console.log('initializing ', this.paths.channels)
2021
fs.writeFileSync(this.paths.channels, '[]')
2122
}
23+
if (!fs.existsSync(this.paths.settings)) {
24+
console.log('initializing ', this.paths.settings)
25+
fs.writeFileSync(this.paths.settings, JSON.stringify({"videoQuality":720}, null, 2))
26+
}
2227

2328
try {
2429
this.videos = JSON.parse(fs.readFileSync(this.paths.videos))
2530
console.log('read videos', this.videos.length)
2631
this.channels = JSON.parse(fs.readFileSync(this.paths.channels))
2732
console.log('read channels', this.channels.length)
33+
this.settings = JSON.parse(fs.readFileSync(this.paths.settings))
34+
console.log('read settings', this.settings)
2835
} catch (err) {
2936
console.error(err)
3037
}
@@ -53,6 +60,10 @@ export default class Repository {
5360
fs.writeFileSync(this.paths.channels, JSON.stringify(this.channels, null, 2))
5461
console.log('saving channels', this.channels.length)
5562
}
63+
saveSettings() {
64+
fs.writeFileSync(this.paths.settings, JSON.stringify(this.settings, null, 2))
65+
console.log('saving settings', this.settings)
66+
}
5667
getVideos () {
5768
const videos = this.videos
5869
.sort((a, b) => +new Date(b.publishedAt) - +new Date(a.publishedAt))
@@ -142,4 +153,16 @@ export default class Repository {
142153
if (this.getVideo(video.id)) return video
143154
return Object.assign(video, {addedAt: Date.now()})
144155
}
156+
getVideoQuality() {
157+
return this.settings.videoQuality
158+
}
159+
setVideoQuality(videoQuality) {
160+
if ([360, 480, 720, 1080, 1440, 2160].includes(videoQuality)) {
161+
this.settings.videoQuality = videoQuality
162+
this.saveSettings()
163+
} else {
164+
console.log('Invalid video quality', videoQuality)
165+
}
166+
return this.settings.videoQuality
167+
}
145168
}

lib/server.js

+28-4
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,18 @@ export function createServer ({repo, connections = []}) {
4848
return deleteVideoHandler(req, res, repo, connections)
4949
if (url.pathname === '/api/videos' && req.method === 'GET')
5050
return allVideosHandler(req, res, repo, connections)
51-
if (url.pathname.match(/\/videos\/.*/) && req.method === 'GET')
52-
return watchVideoHandler(req, res)
53-
if (url.pathname.match(/\/captions\/.*/) && req.method === 'GET')
54-
return captionsHandler(req, res)
51+
if (url.pathname === '/api/video-quality' && req.method === 'GET')
52+
return getVideoQualityHandler(req, res, repo, connections)
53+
if (url.pathname === '/api/video-quality' && req.method === 'POST')
54+
return setVideoQualityHandler(req, res, repo, connections)
5555
if (url.pathname === '/api/disk-usage' && req.method === 'GET')
5656
return diskUsageHandler(req, res, repo, connections)
5757
if (url.pathname === '/api/reclaim-disk-space' && req.method === 'POST')
5858
return reclaimDiskSpaceHandler(req, res, repo, connections)
59+
if (url.pathname.match(/\/videos\/.*/) && req.method === 'GET')
60+
return watchVideoHandler(req, res)
61+
if (url.pathname.match(/\/captions\/.*/) && req.method === 'GET')
62+
return captionsHandler(req, res)
5963

6064
/* client */
6165
if (url.pathname === '/main.css')
@@ -222,6 +226,8 @@ export function createServer ({repo, connections = []}) {
222226
}
223227
}
224228
res.setHeader("content-type", contentType)
229+
// bust cache
230+
res.setHeader('cache-control', 'public, max-age=0')
225231
const stat = fs.statSync(location)
226232

227233
let contentLength = stat.size
@@ -267,6 +273,24 @@ export function createServer ({repo, connections = []}) {
267273
res.end(JSON.stringify(videos))
268274
}
269275

276+
function getVideoQualityHandler(req, res) {
277+
const videoQuality = repo.getVideoQuality()
278+
res.writeHead(200, { 'Content-Type': 'application/json' })
279+
res.end(JSON.stringify(videoQuality))
280+
}
281+
async function setVideoQualityHandler(req, res) {
282+
const body = await getBody(req)
283+
const videoQuality = JSON.parse(body)
284+
285+
const newQuality = repo.setVideoQuality(videoQuality)
286+
if (!newQuality) {
287+
res.writeHead(400)
288+
return res.end()
289+
}
290+
res.writeHead(200, { 'Content-Type': 'application/json' })
291+
res.end(JSON.stringify(newQuality))
292+
}
293+
270294
function diskUsageHandler(req, res) {
271295
const videos = repo.getVideos()
272296
const ignoredVideos = videos.filter(video => video.downloaded)

lib/youtube.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ const fetchYoutubeHeaders = {
88
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'
99
}
1010
const subtitlesYTDLPArgs = `--write-subs --write-auto-subs --sub-format vtt --convert-subs srt -k`
11-
const videoYTDLPArgs = `--concurrent-fragments 5 --merge-output-format webm/mp4 -f bestvideo[height<=720]+bestaudio/best[height<=720] --sponsorblock-remove sponsor`
11+
const videoYTDLPArgs = quality => `--concurrent-fragments 5 --merge-output-format webm/mp4 -f bestvideo[height<=${quality}]+bestaudio/best[height<=${quality}] --sponsorblock-remove sponsor`
12+
1213

1314
function getOptionalCookiesPath() {
1415
if (fs.existsSync('/app/cookies.txt')) return '/app/cookies.txt'
@@ -25,7 +26,8 @@ export async function downloadVideo(id, repo, callback = () => {}) {
2526
try {
2627
const cookiesPath = getOptionalCookiesPath()
2728
let cookiesOption = cookiesPath ? `--cookies ${cookiesPath}` : ''
28-
const commandArgs = `-o ./data/videos/${id}.%(ext)s ${cookiesOption} ${videoYTDLPArgs} ${subtitlesYTDLPArgs} -- ${id}`.split(/ +/)
29+
const quality = repo.getVideoQuality()
30+
const commandArgs = `-o ./data/videos/${id}.%(ext)s ${cookiesOption} ${videoYTDLPArgs(quality)} ${subtitlesYTDLPArgs} -- ${id}`.split(/ +/)
2931
console.log('running yt-dlp', commandArgs.join(' '))
3032
let location, format
3133
for await (const line of spawn('yt-dlp', commandArgs)) {

test/repository.test.js

+8
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,12 @@ test('updates single video by id', () => {
9898
repo.upsertVideos(video1)
9999
const updatedVideo = repo.updateVideo(video1.id, { title: 'Updated Title' })
100100
assert.equal(updatedVideo.title, 'Updated Title')
101+
})
102+
103+
test('gets video quality', () => {
104+
assert.equal(repo.getVideoQuality(), 720)
105+
})
106+
test('sets video quality', () => {
107+
assert.ok(repo.setVideoQuality(1080))
108+
assert.equal(repo.getVideoQuality(), 1080)
101109
})

0 commit comments

Comments
 (0)