index.php (9745B)
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 passthru(
168 "yt-dlp " .
169 "-x " .
170 "--audio-format opus " .
171 "-o '%(id)s.%(ext)s' " .
172 "https://www.youtube.com/watch?v=" . basename($_GET["video"])
173 );
174 }
175 // If file has been downloaded properly, check whether the file is valid
176 if (!(analyze_video(basename($_GET["video"]) . ".opus")["playtime_seconds"] > 0)) {
177 // Remove if unvalid
178 unlink(basename($_GET["video"]) . ".opus");
179 }
180 // If file still exists, return to user
181 if (file_exists(basename($_GET["video"]) . ".opus")) {
182 header("content-type: audio/ogg; codec=opus");
183 header(
184 "content-length: " . filesize(basename($_GET["video"]) . ".opus")
185 );
186 header(
187 "content-disposition: inline; filename=" .
188 basename($_GET["video"]) . ".opus"
189 );
190 readfile(basename($_GET['video']) . ".opus");
191 } else {
192 // otherwise return error and exit
193 http_response_code(404);
194 }
195 die();
196 } else {
197 ?>
198
199 <html>
200 <head>
201 <title>yt2rss</title>
202 <link
203 rel="stylesheet"
204 type="text/css"
205 href="/assets/css/simple.min.css"
206 />
207 </head>
208
209 <body>
210 <h1>yt2rss</h1>
211 <p><a href="https://src.jayvii.de/pub/yt2rss">Learn more</a></p>
212
213 <p>Usage:</p>
214 <ul>
215 <li>
216 <?php echo "https://" .
217 $_SERVER["SERVER_NAME"] .
218 "/?channel=UCt_Y3j8CpXx366et1RaHACA";
219 ?>
220 </li>
221 <li>
222 <?php echo "https://" .
223 $_SERVER["SERVER_NAME"] .
224 "/?video=TV8tEq56vHI";
225 ?>
226 </li>
227 </ul>
228
229 <p>Videos can be included or excluded based on title-matches:</p>
230 <ul>
231 <li>
232 Include only videos whose title contain "vlog time":<br>
233 <?php
234 echo "https://" .
235 $_SERVER["SERVER_NAME"] .
236 "/?channel=UCt_Y3j8CpXx366et1RaHACA&include=vlog%20time";
237 ?>
238 </li>
239 <li>
240 Exclude all videos whose title contain "vlog time":<br>
241 <?php
242 echo "https://" .
243 $_SERVER["SERVER_NAME"] .
244 "/?channel=UCt_Y3j8CpXx366et1RaHACA&exclude=vlog%20time";
245 ?>
246 </li>
247 </ul>
248
249 <?php
250 // Authentification
251 if (!is_null($auth_key)) {
252 ?>
253 <p>
254 This Service requires an authentification key. If you have one,
255 add it to the request URLs with:
256 </p>
257 <ul>
258 <li>
259 <?php
260 echo "https://" .
261 $_SERVER["SERVER_NAME"] .
262 "/?auth=MY_KEY&channel=UCt_Y3j8CpXx366et1RaHACA";
263 ?>
264 </li>
265 <li>
266 <?php
267 echo "https://" .
268 $_SERVER["SERVER_NAME"] .
269 "/?auth=MY_KEY&video=TV8tEq56vHI";
270 ?>
271 </li>
272 </ul>
273 <p>
274 If you do not have an authentification key, please contact the
275 server admin.
276 </p>
277 <?php
278 if ($auth) {
279 ?>
280 <p style="color:white;background-color:green;">
281 You are authenticated :)
282 </p>
283 <?php
284 } else {
285 ?>
286 <p style='color:white;background-color:red;'>
287 You are NOT authenticated :(
288 </p>
289 <?php
290 }
291 }
292 ?>
293 </body>
294 </html>
295 <?php
296 }
297 ?>