1 /** 2 Set of utilties for interfacing with the youtube-dl command line program. 3 */ 4 5 module dscord.voice.youtubedl; 6 7 import dcad.types : DCAFile, rawReadFramesFromFile; 8 import vibe.core.core, 9 vibe.core.concurrency; 10 11 import dscord.types, 12 dscord.util.process; 13 14 shared struct WorkerState { 15 } 16 17 bool maybeSendCompat(T)(Task dest, T data) { 18 if (!dest.running) return false; 19 dest.sendCompat(data); 20 return true; 21 } 22 23 class YoutubeDL { 24 static void infoWorker(Task parent, string url) { 25 auto proc = new Process([ 26 "youtube-dl", 27 "-i", 28 "-j", 29 "-4", 30 "--quiet", 31 "--no-check-certificate", 32 "--no-warnings", 33 "--audio-format", "mp3", 34 "--youtube-skip-dash-manifest", 35 url]); 36 37 shared string[] lines; 38 while (!proc.stdout.eof()) { 39 if (!parent.maybeSendCompat!string(proc.stdout.readln())) { 40 proc.wait(); 41 return; 42 } 43 } 44 45 parent.maybeSendCompat(null); 46 47 // Let the process terminate 48 proc.wait(); 49 } 50 51 /** 52 Loads songs from a given youtube-dl compatible URL, calling a delegate with 53 each song. This function is useful for downloading large playlists where 54 waiting for all the songs to be processed takes a long time. When downloading 55 is completed, the delegate `complete` will be called with the total number of 56 songs downloaded/pasred. 57 58 Params: 59 url = url of playlist or song to download 60 cb = delegate taking a VibeJSON object for each song downloaded from the URL. 61 complete = delegate taking a size_t, called when completed (with the total 62 number of downloaded songs) 63 */ 64 static void getInfoAsync(string url, void delegate(VibeJSON) cb, void delegate(size_t) complete=null) { 65 Task worker = runWorkerTaskH(&YoutubeDL.infoWorker, Task.getThis, url); 66 67 size_t count = 0; 68 while (true) { 69 try { 70 string line = receiveOnlyCompat!(string); 71 runTask(cb, parseJsonString(line)); 72 count += 1; 73 } catch (MessageMismatch e) { 74 break; 75 } catch (Exception e) {} 76 } 77 78 if (complete) complete(count); 79 } 80 81 /** 82 Returns a VibeJSON object with information for a given URL. 83 */ 84 static VibeJSON[] getInfo(string url) { 85 VibeJSON[] result; 86 87 Task worker = runWorkerTaskH(&YoutubeDL.infoWorker, Task.getThis, url); 88 89 while (true) { 90 try { 91 string line = receiveOnlyCompat!(string); 92 result ~= parseJsonString(line); 93 } catch (MessageMismatch e) { 94 break; 95 } catch (Exception e) {} 96 } 97 98 return result; 99 } 100 101 static void downloadWorker(Task parent, string url) { 102 auto chain = new ProcessChain(). 103 run(["youtube-dl", "-v", "-f", "bestaudio", "-o", "-", url]). 104 run(["ffmpeg", "-i", "pipe:0", "-f", "s16le", "-ar", "48000", "-ac", "2", "pipe:1", "-vol", "100"]). 105 run(["dcad"]); 106 107 shared ubyte[][] frames = cast(shared ubyte[][])rawReadFramesFromFile(chain.end); 108 parent.maybeSendCompat!(shared ubyte[][])(frames); 109 110 // Let the process chain terminate 111 chain.wait(); 112 } 113 114 /** 115 Downloads and encodes a given URL into a playable format. This function spawns 116 a new worker thread to download and encode a given youtube-dl compatabile 117 URL. 118 */ 119 static DCAFile download(string url) { 120 Task worker = runWorkerTaskH(&YoutubeDL.downloadWorker, Task.getThis, url); 121 auto frames = receiveOnlyCompat!(shared ubyte[][])(); 122 return new DCAFile(cast(ubyte[][])frames); 123 } 124 }