aboutsummaryrefslogtreecommitdiffstats
path: root/src/server/Cache.hx
diff options
context:
space:
mode:
authorRblSb <msrblsb@gmail.com>2025-01-26 23:22:33 +0300
committerRblSb <msrblsb@gmail.com>2025-01-28 15:46:30 +0300
commit0592564264fff57ccfd9677957196951f9f1c6cf (patch)
treec360c2e5d45d9ac8706f836b0466b88221e1f10d /src/server/Cache.hx
parentc7518e58788c17ad2ca8340ab5c7633489aa9518 (diff)
Video upload feature
And you can play video as soon as upload starts! This is pretty useful for thicc ones. Video will be keeped in cache and will comply cache size limit. I'm also implemented system storage check to change cache limit if it's lower than config value, so upload will be blocked if there is lower than 10MiB available on disk.
Diffstat (limited to 'src/server/Cache.hx')
-rw-r--r--src/server/Cache.hx139
1 files changed, 110 insertions, 29 deletions
diff --git a/src/server/Cache.hx b/src/server/Cache.hx
index 450a893..fa5889c 100644
--- a/src/server/Cache.hx
+++ b/src/server/Cache.hx
@@ -1,5 +1,6 @@
package server;
+import haxe.io.Path;
import js.lib.Promise;
import js.node.ChildProcess;
import js.node.Fs.Fs;
@@ -16,7 +17,8 @@ class Cache {
public final isYtReady = false;
/** In bytes **/
- public var storageLimit = 3 * 1024 * 1024 * 1024;
+ var storageLimit = 3 * 1024 * 1024 * 1024;
+ final freeSpaceBlock = 10 * 1024 * 1024; // 10MB
public function new(main:Main, cacheDir:String) {
this.main = main;
@@ -55,16 +57,22 @@ class Cache {
return;
}
final outName = videoId + ".mp4";
- if (cachedFiles.contains(outName) && FileSystem.exists('$cacheDir/$outName')) {
+ if (cachedFiles.contains(outName) && isFileExists(outName)) {
callback(outName);
return;
}
final ytdl:Dynamic = untyped require("@distube/ytdl-core");
- log(client, 'Caching $url to $outName...');
- // final opts = {playerClients: ["IOS", "WEB_CREATOR"]};
+ trace('Caching $url to $outName...');
+ main.send(client, {
+ type: Progress,
+ progress: {
+ type: Caching,
+ ratio: 0,
+ data: outName
+ }
+ });
final promise:Promise<YouTubeVideoInfo> = ytdl.getInfo(url);
promise.then(info -> {
- // trace(info.formats.filter(item -> item.audioCodec != null));
trace('Get info with ${info.formats.length} formats');
final audioFormat:YoutubeVideoFormat = try {
ytdl.chooseFormat(info.formats.filter(item -> {
@@ -73,26 +81,23 @@ class Cache {
} catch (e) {
log(client, "Error: audio format not found");
trace(e);
- trace(info.formats);
+ trace(info.formats.filter(item -> item.hasAudio));
return;
}
final videoFormat = getBestYoutubeVideoFormat(info.formats) ?? {
log(client, "Error: video format not found");
- trace(info.formats);
+ trace(info.formats.filter(item -> item.hasVideo));
return;
}
- trace("Picked audio and video formats");
final dlVideo:Readable<Dynamic> = ytdl(url, {
format: videoFormat,
- // playerClients: opts.playerClients
});
dlVideo.pipe(Fs.createWriteStream('$cacheDir/input-video'));
dlVideo.on("error", err -> log(client, "Error during video download: " + err));
final dlAudio:Readable<Dynamic> = ytdl(url, {
format: audioFormat,
- // playerClients: opts.playerClients
});
dlAudio.pipe(Fs.createWriteStream('$cacheDir/input-audio'));
dlAudio.on("error", err -> log(client, "Error during audio download: " + err));
@@ -100,8 +105,13 @@ class Cache {
var count = 0;
function onComplete(type:String):Void {
count++;
- log(client, '$type track downloaded ($count/2)');
+ trace('$type track downloaded ($count/2)');
if (count < 2) return;
+ var size = FileSystem.stat('$cacheDir/input-video').size;
+ size += FileSystem.stat('$cacheDir/input-audio').size;
+ // clean some space for full mp4
+ removeOlderCache(size + freeSpaceBlock);
+
final args = '-y -i input-video -i input-audio -c copy -map 0:v -map 1:a ./$outName'.split(" ");
final process = ChildProcess.spawn("ffmpeg", args, {
cwd: cacheDir,
@@ -117,40 +127,111 @@ class Cache {
}
final inVideo = '$cacheDir/input-video';
final inAudio = '$cacheDir/input-audio';
- if (FileSystem.exists(inVideo)) FileSystem.deleteFile(inVideo);
- if (FileSystem.exists(inAudio)) FileSystem.deleteFile(inAudio);
+ FileSystem.deleteFile(inVideo);
+ FileSystem.deleteFile(inAudio);
- if (!cachedFiles.contains(outName)) {
- cachedFiles.unshift(outName);
- }
- removeOlderCache();
+ add(outName);
callback(outName);
});
}
dlVideo.on("finish", () -> onComplete("Video"));
dlAudio.on("finish", () -> onComplete("Audio"));
- // dlVideo.on('progress', (c, d, t) -> {
- // final progress = Std.int((d / t * 100) * 10) / 10;
- // trace(progress);
- // });
+ var isAudioStart = true;
+ dlAudio.on("progress", (chunkLength:Int, downloaded:Int, contentLength:Int) -> {
+ if (isAudioStart) {
+ isAudioStart = false;
+ removeOlderCache(contentLength);
+ }
+ });
+ var isVideoStart = true;
+ dlVideo.on("progress", (chunkLength:Int, downloaded:Int, contentLength:Int) -> {
+ if (isVideoStart) {
+ isVideoStart = false;
+ removeOlderCache(contentLength);
+ }
+ final ratio = (downloaded / contentLength).clamp(0, 1);
+ main.send(client, {
+ type: Progress,
+ progress: {
+ type: Downloading,
+ ratio: ratio
+ }
+ });
+ });
}).catchError(err -> {
log(client, "" + err);
});
}
- function removeOlderCache():Void {
- while (getUsedSpace() > storageLimit) {
- final name = cachedFiles.pop();
- final path = '$cacheDir/$name';
+ public function setStorageLimit(bytes:Int) {
+ storageLimit = cast bytes;
+ storageLimit = storageLimit.limitMin(0);
+ final statfs = (Fs : Dynamic).statfs ?? return;
+ statfs("/", (err, stats) -> {
+ if (err != null) {
+ trace(err);
+ return;
+ }
+ final availSpace = (stats.bsize * stats.bavail - freeSpaceBlock).limitMin(0);
+ removeOlderCache();
+ final freeSpace = getFreeSpace();
+ if (availSpace < freeSpace) {
+ // shrink limit lower than disk space
+ storageLimit += availSpace - freeSpace;
+ storageLimit = storageLimit.limitMin(0);
+ removeOlderCache();
+ }
+ });
+ }
+
+ public function add(name:String) {
+ if (!cachedFiles.contains(name)) {
+ cachedFiles.unshift(name);
+ }
+ }
+
+ public function removeOlderCache(addFileSize = 0):Void {
+ var space = getUsedSpace(addFileSize);
+ while (space > storageLimit) {
+ final name = cachedFiles.pop() ?? break;
+ final path = getFilePath(name);
if (FileSystem.exists(path)) FileSystem.deleteFile(path);
+ space = getUsedSpace(addFileSize);
}
}
- function getUsedSpace():Int {
- var total = 0;
+ public function getFreeFileName(baseName = "video"):String {
+ var i = 1;
+ while (true) {
+ final n = i == 1 ? "" : '$i';
+ final name = '$baseName$n.mp4';
+ if (!isFileExists(name)) return name;
+ i++;
+ }
+ }
+
+ public function getFilePath(name:String):String {
+ return '$cacheDir/$name';
+ }
+
+ public function getFileUrl(name:String):String {
+ final folder = Path.withoutDirectory(cacheDir);
+ return '/$folder/$name';
+ }
+
+ public function isFileExists(name:String):Bool {
+ return FileSystem.exists(getFilePath(name));
+ }
+
+ public function getFreeSpace():Int {
+ return storageLimit - getUsedSpace();
+ }
+
+ public function getUsedSpace(addFileSize = 0):Int {
+ var total = addFileSize.limitMin(0);
for (name in cachedFiles.reversed()) {
- final path = '$cacheDir/$name';
+ final path = getFilePath(name);
if (!FileSystem.exists(path)) {
cachedFiles.remove(name);
continue;
@@ -161,7 +242,7 @@ class Cache {
}
function getBestYoutubeVideoFormat(formats:Array<YoutubeVideoFormat>):Null<YoutubeVideoFormat> {
- final qPriority = [1080, 720, 480, 360, 240];
+ final qPriority = [1080, 720, 480, 360, 240, 144];
for (q in qPriority) {
final quality = '${q}p';
for (format in formats) {
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage