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 }