index.php (10053B)
1 <?php
2 /*
3 * SPDX-FileCopyrightText: 2024 JayVii <jayvii[AT]posteo[DOT]de>
4 * SPDX-License-Identifier: AGPL-3.0-or-later
5 */
6
7 if (file_exists("./env.php")) {
8 include "./env.php";
9 }
10
11 function analyze_video($video_file) {
12 include_once("3rdparty/getid3/getid3/getid3.php");
13 $getID3 = new getID3;
14 $video_info = $getID3->analyze($video_file);
15 return $video_info;
16 }
17
18 // Authentification
19 if ($_GET["auth"] != $auth_key && !is_null($auth_key)) {
20 $auth = false;
21 } else {
22 $auth = true;
23 }
24
25 if (!is_null($_GET["channel"]) && $auth) {
26
27 // Fetch Youtube XML Feed
28 $channel_xml = file(
29 "https://www.youtube.com/feeds/videos.xml?channel_id=" . basename($_GET["channel"])
30 );
31 // Replace un-parsable items
32 $channel_xml = str_replace(
33 array("yt:", "media:"),
34 array("yt_", "media_"),
35 $channel_xml
36 );
37 // Cast Array to string
38 $channel_xml = implode(PHP_EOL, $channel_xml);
39 // Parse XML
40 $channel_xml = simplexml_load_string($channel_xml);
41 $channel_xml = json_encode($channel_xml);
42 $channel_xml = json_decode($channel_xml, true);
43
44
45 // Construct Podcatcher XML
46 $rss_xml = "<rss " .
47 "version=\"2.0\" " .
48 "xmlns:atom=\"http://www.w3.org/2005/Atom\" " .
49 "xmlns:content=\"http://purl.org/rss/1.0/modules/content/\" " .
50 "xmlns:itunes=\"http://www.itunes.com/dtds/podcast-1.0.dtd\" " .
51 "xmlns:dc=\"http://purl.org/dc/elements/1.1/\" " .
52 "xmlns:podcast=\"https://podcastindex.org/namespace/1.0\"" .
53 ">\n<channel>\n";
54 $rss_xml = $rss_xml .
55 "<docs>http://www.rssboard.org/rss-specification</docs>\n";
56 $rss_xml = $rss_xml . "<title>" .
57 str_replace(
58 array("&"),
59 "&",
60 $channel_xml["title"]
61 ) . "</title>\n";
62 $channel_id = str_replace(
63 array("yt_channel:"),
64 "",
65 $channel_xml["id"]
66 );
67 $rss_xml = $rss_xml . "<link>https://www.youtube.com/channel/" .
68 basename($_GET["channel"]) . "</link>\n";
69 $rss_xml = $rss_xml . "<description>" .
70 str_replace(
71 array("&"),
72 "&",
73 $channel_xml["title"]
74 ) .
75 "</description>\n";
76 $rss_xml = $rss_xml . "<pubDate>" . $channel_xml["published"] .
77 "</pubDate>\n";
78 // FIXME: fetch channel image rather than first video image
79 $video_id = str_replace(
80 array("yt_video:"),
81 "",
82 $channel_xml["entry"][0]["id"]
83 );
84 $rss_xml = $rss_xml . "<itunes:image href=\"https://i4.ytimg.com/vi/" .
85 $video_id . "/hqdefault.jpg\"/>\n";
86 $rss_xml = $rss_xml . "<atom:link href=\"https://" .
87 $_SERVER["SERVER_NAME"] .
88 "/?channel=" . basename($_GET["channel"]);
89 if (!is_null($auth_key)) {
90 $rss_xml = $rss_xml . "&auth=" . $auth_key;
91 }
92 $rss_xml = $rss_xml . "\"" .
93 " rel=\"self\" type=\"application/rss+xml\"/>\n";
94
95 // Add media items
96 foreach ($channel_xml["entry"] as $entry) {
97 // Skip if title matches "exclude"
98 if (!is_null($_GET["exclude"])) {
99 $excluded = strstr($entry["title"], rawurldecode($_GET["exclude"]));
100 if ($excluded) continue;
101 }
102 // Skip if title does not match "include"
103 if (!is_null($_GET["include"])) {
104 $included = strstr($entry["title"], rawurldecode($_GET["include"]));
105 if (!$included) continue;
106 }
107 $video_id = str_replace(array("yt_video:"), "", $entry["id"]);
108 // Get Video Length, size and type
109 if (file_exists($video_id . ".opus")) {
110 $video_info = analyze_video($video_id . ".opus");
111 $video_size = $video_info["filesize"];
112 $video_duration = $video_info["playtime_string"];
113 $video_ftype = $video_info["mime_type"];
114 } else {
115 $video_size = 0;
116 $video_duration = "00:00:00";
117 $video_ftype = "audio/ogg";
118 }
119 $rss_xml = $rss_xml . "<item>\n";
120 $rss_xml = $rss_xml . "<title>" .
121 str_replace(array("&"), "&", $entry["title"]) . "</title>\n";
122 // FIXME: fetch true description!
123 $rss_xml = $rss_xml . "<description>" .
124 "Video-Link:" . PHP_EOL .
125 $entry["link"]["@attributes"]["href"] . PHP_EOL . PHP_EOL .
126 str_replace(
127 array("&"),
128 "&",
129 $entry["media_group"]["media_description"]
130 ) . "</description>\n";
131 $rss_xml = $rss_xml . "<itunes:author>" .
132 str_replace(
133 array("&"),
134 "&",
135 $entry["author"]["name"]
136 ) .
137 "</itunes:author>\n";
138 $rss_xml = $rss_xml . "<pubDate>" . $entry["published"] .
139 "</pubDate>\n";
140 $rss_xml = $rss_xml . "<itunes:image href=\"https://i1.ytimg.com/vi/" .
141 $video_id . "/hqdefault.jpg\"/>\n";
142 $rss_xml = $rss_xml . "<enclosure url=\"https://" .
143 $_SERVER["SERVER_NAME"] .
144 "/?video=" . $video_id;
145 if (!is_null($auth_key)) {
146 $rss_xml = $rss_xml . "&auth=" . $auth_key;
147 }
148 $rss_xml = $rss_xml . "\"".
149 " type=\"" . $video_ftype . "\" length=\"" . $video_size . "\"/>\n";
150 $rss_xml = $rss_xml . "<itunes:duration>" . $video_duration .
151 "</itunes:duration>\n";
152 $rss_xml = $rss_xml . "</item>\n";
153 }
154
155 $rss_xml = $rss_xml . "</channel>\n</rss>\n";
156 header("Content-type: application/xml");
157 print_r($rss_xml);
158 die();
159
160 } else if (!is_null($_GET["video"]) && $auth) {
161 $download_retry = 0;
162 while (
163 !file_exists(basename($_GET["video"]) . ".opus") &&
164 $download_retry <= 3
165 ) {
166 $download_retry++;
167 if (
168 strlen(
169 system("ps ax | grep -v grep | grep " . basename($_GET["video"]))
170 ) < 1
171 ) {
172 passthru(
173 "yt-dlp " .
174 "-x " .
175 "--audio-format opus " .
176 "-o '%(id)s.%(ext)s' " .
177 "https://www.youtube.com/watch?v=" . basename($_GET["video"])
178 );
179 }
180 }
181 // If file has been downloaded properly, check whether the file is valid
182 if (!(analyze_video(basename($_GET["video"]) . ".opus")["playtime_seconds"] > 0)) {
183 // Remove if unvalid
184 unlink(basename($_GET["video"]) . ".opus");
185 }
186 // If file still exists, return to user
187 if (
188 file_exists(basename($_GET["video"]) . ".opus") &&
189 strlen(
190 system("ps ax | grep -v grep | grep " . basename($_GET["video"]))
191 ) < 1
192 ) {
193 header("content-type: audio/ogg; codec=opus");
194 header(
195 "content-length: " . filesize(basename($_GET["video"]) . ".opus")
196 );
197 header(
198 "content-disposition: inline; filename=" .
199 basename($_GET["video"]) . ".opus"
200 );
201 readfile(basename($_GET['video']) . ".opus");
202 } else {
203 // otherwise return error and exit
204 http_response_code(404);
205 }
206 die();
207 } else {
208 ?>
209
210 <html>
211 <head>
212 <title>yt2rss</title>
213 <link
214 rel="stylesheet"
215 type="text/css"
216 href="/assets/css/simple.min.css"
217 />
218 </head>
219
220 <body>
221 <h1>yt2rss</h1>
222 <p><a href="https://src.jayvii.de/pub/yt2rss">Learn more</a></p>
223
224 <p>Usage:</p>
225 <ul>
226 <li>
227 <?php echo "https://" .
228 $_SERVER["SERVER_NAME"] .
229 "/?channel=UCt_Y3j8CpXx366et1RaHACA";
230 ?>
231 </li>
232 <li>
233 <?php echo "https://" .
234 $_SERVER["SERVER_NAME"] .
235 "/?video=TV8tEq56vHI";
236 ?>
237 </li>
238 </ul>
239
240 <p>Videos can be included or excluded based on title-matches:</p>
241 <ul>
242 <li>
243 Include only videos whose title contain "vlog time":<br>
244 <?php
245 echo "https://" .
246 $_SERVER["SERVER_NAME"] .
247 "/?channel=UCt_Y3j8CpXx366et1RaHACA&include=vlog%20time";
248 ?>
249 </li>
250 <li>
251 Exclude all videos whose title contain "vlog time":<br>
252 <?php
253 echo "https://" .
254 $_SERVER["SERVER_NAME"] .
255 "/?channel=UCt_Y3j8CpXx366et1RaHACA&exclude=vlog%20time";
256 ?>
257 </li>
258 </ul>
259
260 <?php
261 // Authentification
262 if (!is_null($auth_key)) {
263 ?>
264 <p>
265 This Service requires an authentification key. If you have one,
266 add it to the request URLs with:
267 </p>
268 <ul>
269 <li>
270 <?php
271 echo "https://" .
272 $_SERVER["SERVER_NAME"] .
273 "/?auth=MY_KEY&channel=UCt_Y3j8CpXx366et1RaHACA";
274 ?>
275 </li>
276 <li>
277 <?php
278 echo "https://" .
279 $_SERVER["SERVER_NAME"] .
280 "/?auth=MY_KEY&video=TV8tEq56vHI";
281 ?>
282 </li>
283 </ul>
284 <p>
285 If you do not have an authentification key, please contact the
286 server admin.
287 </p>
288 <?php
289 if ($auth) {
290 ?>
291 <p style="color:white;background-color:green;">
292 You are authenticated :)
293 </p>
294 <?php
295 } else {
296 ?>
297 <p style='color:white;background-color:red;'>
298 You are NOT authenticated :(
299 </p>
300 <?php
301 }
302 }
303 ?>
304 </body>
305 </html>
306 <?php
307 }
308 ?>