PageRenderTime 43ms CodeModel.GetById 6ms RepoModel.GetById 0ms app.codeStats 0ms

/old-php/restreamer.php

https://github.com/icedream/restreamer
PHP | 284 lines | 237 code | 19 blank | 28 comment | 25 complexity | 00e888b4bae378653ca0bb188b4824b4 MD5 | raw file
Possible License(s): GPL-3.0
  1. <?php
  2. /**
  3. * Restreamer
  4. *
  5. * @author Carl Kittelberger
  6. * @email icedream@blazing.de
  7. * @version 0.1a
  8. * @package restreamer
  9. */
  10. // Help!
  11. if($argc < 1)
  12. {
  13. echo 'Usage: ./'.array_pop(explode("/",__FILE__))
  14. .' [switches]'
  15. .' <config-file>';
  16. echo '';
  17. echo "POSSIBLE SWITCHES:";
  18. echo "\t-d";
  19. echo "\t\tEnables debug mode.";
  20. exit;
  21. }
  22. // Switches
  23. define("DEBUG", in_array("-d", $argv));
  24. // Configuration file
  25. require_once(array_pop($argv));
  26. if(empty($_SOURCE))
  27. {
  28. error("You need to input the source data into the configuration!");
  29. exit;
  30. }
  31. if(empty($_TARGETS))
  32. warn("No targets defined. This will just download the stream for nothing.");
  33. // Connect to the source
  34. $_SOURCE["socket"] = fsockopen($_SOURCE["host"], $_SOURCE["port"], $_SOURCE["errno"], $_SOURCE["error"]);
  35. $s = &$_SOURCE["socket"];
  36. if(empty($_SOURCE["mountpoint"]))
  37. $_SOURCE["mountpoint"] = "/";
  38. fputs($s, "GET " . $_SOURCE["mountpoint"] . " HTTP/1.1\r\n");
  39. fputs($s, "Host: " . $_SOURCE["host"] . "\r\n");
  40. fputs($s, "User-Agent: GWRestreaming/0.1a\r\n");
  41. fputs($s, "Connection: close\r\n");
  42. if($_SOURCE["relay-metadata"])
  43. fputs($s, "icy-metadata:1\r\n");
  44. fputs($s, "\r\n");
  45. if(empty($_SOURCE["headers"]))
  46. $_SOURCE["headers"] = Array();
  47. $l = fgets($s);
  48. if(substr($l, 9, 3) != "200")
  49. {
  50. echo "Can't connect to the source, HTTP error code " . substr($l,9,3) . "\r\n";
  51. exit;
  52. }
  53. // Source header analysis
  54. $l = trim(fgets($s));
  55. while($l != "")
  56. {
  57. list($headName) = explode(":", $l);
  58. $headName = strtolower(trim($headName));
  59. $headValue = trim(substr($l, strlen($headName) + 1));
  60. if(substr($headName, 0, 4) == "icy-" || substr($headName, 0, 4) == "ice-" || $headName=="content-type")
  61. {
  62. if($headName == "icy-metaint")
  63. {
  64. debug("Given metadata interval is $headValue");
  65. $_SOURCE["metaint"] = intval($headValue);
  66. }
  67. else if(!isset($_SOURCE["headers"][$headName]))
  68. {
  69. debug("Received header $headName");
  70. $_SOURCE["headers"][$headName] = $headValue;
  71. }
  72. else debug("Received header $headName, but its value has been fixed by configuration");
  73. } else debug("Ignoring header $headName");
  74. $l = trim(fgets($s));
  75. }
  76. // Validation
  77. if($_SOURCE["headers"]["content-type"] == "audio/ogg" && $_SOURCE["relay-metadata"])
  78. {
  79. //die("This stream is an ogg stream - normally without any metadata packed into the ICY protocol -, means you should set the \"relay-metadata\" option to false.");
  80. $_SOURCE["relay-metadata"] = false;
  81. warn("Forcing \"relay-metadata\" to false since stream is in an OGG container providing all metadata outside of the protocol.");
  82. }
  83. // Initialize targets
  84. $connServers = Array();
  85. foreach($_TARGETS as $target)
  86. {
  87. if(empty($target["username"]))
  88. $target["username"] = "source";
  89. if(@$target["relay-metadata"] === null)
  90. $target["relay-metadata"] = $_SOURCE["relay-metadata"];
  91. if(empty($target["mountpoint"]))
  92. $target["mountpoint"] = "/"; // just for the notices
  93. $target["socket"] = fsockopen($target["host"], $target["port"], $target["errno"], $target["error"], 4);
  94. // Possible types should be: shoutcast, icecast2
  95. $target["type"] = strtolower($target["type"]);
  96. if($target["type"] == "shoutcast") {
  97. fputs($target["socket"], $target["password"] . "\r\n");
  98. $status = trim(fgets($target["socket"]));
  99. if($status != "OK2")
  100. {
  101. fclose($target["socket"]);
  102. error("Could not connect to ".$target["host"].": Status is $status.");
  103. continue;
  104. }
  105. foreach($_SOURCE["headers"] as $name => $value)
  106. fputs($target["socket"], "$name: $value\r\n");
  107. fputs($target["socket"], "\r\n");
  108. } else {
  109. fputs($target["socket"], "SOURCE " . $target["mountpoint"] . " HTTP/1.1\r\n");
  110. fputs($target["socket"], "Host: " . $target["host"] . "\r\n");
  111. fputs($target["socket"], "Authorization: Basic " . base64_encode($target["username"].":".$target["password"])."\r\n");
  112. foreach($_SOURCE["headers"] as $name => $value)
  113. fputs($target["socket"], "$name: $value\r\n");
  114. // Here I experimented a bit with icy-metaint.
  115. // Pretty interesting that this actually works for source => server.
  116. // Though the server is dumb enough to just ignore the data.
  117. fputs($target["socket"], "\r\n");
  118. $l = fgets($target["socket"]);
  119. if(strstr($l," 200 ")<=0)
  120. {
  121. @fclose($target["socket"]);
  122. error("Could not connect to ".$target["host"].":".$target["port"].$target["mountpoint"].": Server gave back: $l");
  123. continue;
  124. }
  125. }
  126. $status = trim(fgets($target["socket"]));
  127. while($status != "")
  128. {
  129. list($capName, $capValue) = explode($status, ":");
  130. $target["capabilities"][$capName] = trim($capValue);
  131. debug("Received: $capName = $capValue");
  132. $status = trim(fgets($ns));
  133. }
  134. echo "Successfully connected to ".$target["host"].":".$target["port"].$target["mountpoint"]."\r\n";
  135. $connServers[] = $target;
  136. }
  137. echo "Now streaming!\r\n";
  138. $_SOURCE["metadata"] = "";
  139. $metadatalength = 0;
  140. // Listening loop
  141. while($_SOURCE["relay-metadata"])
  142. {
  143. // Receive the metadata!
  144. $recv = $_SOURCE["metaint"];
  145. while($recv > 0)
  146. {
  147. $packet = fread($_SOURCE["socket"], $recv);
  148. $recv -= strlen($packet);
  149. write($packet);
  150. }
  151. $metadatalength = fread($_SOURCE["socket"], 1);
  152. $metadatalength = unpack("Clength", $metadatalength);
  153. $metadatalength = $metadatalength["length"] * 16;
  154. // Parse the metadata!
  155. if($metadatalength > 0)
  156. {
  157. $metadata = "";
  158. $metadatarecv = $metadatalength;
  159. while($metadatarecv > 0)
  160. {
  161. $metadatapacket = fread($_SOURCE["socket"], $metadatarecv);
  162. $metadatarecv -= strlen($metadatapacket);
  163. $metadata .= $metadatapacket;
  164. }
  165. //echo "$metadatarecv Metadata bytes left\r\n";
  166. $metadata = trim($metadata);
  167. $metadata = explode("'", $metadata);
  168. $metadata = $metadata[1];
  169. if($metadata != $_SOURCE["metadata"])
  170. {
  171. echo "New metadata: $metadata\r\n";
  172. $_SOURCE["metadata"] = $metadata;
  173. }
  174. }
  175. metadata($metadata);
  176. }
  177. debug("ICY metadata relaying is off.");
  178. while(true)
  179. {
  180. $p="";
  181. while(strlen($p)<8192)
  182. $p.=fread($_SOURCE["socket"], 8192-strlen($p));
  183. write($p);
  184. }
  185. // TODO: Implement support for Ctrl+C
  186. // Close all connections
  187. foreach($connServers as $a)
  188. fclose($a["socket"]);
  189. fclose($_SOURCE["socket"]);
  190. echo "Streaming stopped.";
  191. /***************
  192. * Functions
  193. ***************/
  194. function debug($content)
  195. {
  196. if(DEBUG) echo "DEBUG: $content\r\n";
  197. }
  198. function warn($content)
  199. {
  200. echo "WARN: $content\r\n";
  201. }
  202. function error($content)
  203. {
  204. echo "ERROR: $content\r\n";
  205. }
  206. function write($content)
  207. {
  208. global $connServers;
  209. foreach($connServers as &$a)
  210. {
  211. if($a == null)
  212. continue;
  213. stream_set_timeout($a["socket"], 0, isset($a["write-timeout"]) ? $a["write-timeout"] : 500);
  214. if(!fwrite($a["socket"], $content))
  215. {
  216. /*
  217. echo "ERROR: Removing " . $a["host"] . ":" . $a["port"].$a["mountpoint"] . " because we got disconnected!\r\n";
  218. $a = null;
  219. */
  220. echo "WARN: Stream error @ ".$a["host"].":".$a["port"].$a["mountpoint"]." - " . $a["error"] . "\r\n";
  221. }
  222. }
  223. }
  224. function metadata($content)
  225. {
  226. global $connServers;
  227. foreach($connServers as &$a)
  228. {
  229. if($a==null) continue;
  230. if((isset($a["metadata"]) && $a["metadata"] == $content) || strlen($content) == 0 /* icecast2's "no metadata change" alias ._. */)
  231. {
  232. // Metadata has not been changed
  233. // I had a little experiment here too thanks to the fact I did not know that the source protocol
  234. // does not even support sending metadata from source to server in a >direct< way.
  235. // For example by injecting it the same way the server does for clients. ._.
  236. } else {
  237. // Metadata has been changed
  238. if($a["type"] == "shoutcast")
  239. {
  240. // Simple http request
  241. $response = file_get_contents("http://" . (isset($a["username"]) ? $a["username"] . "@" : "") . $a["host"] . ":" . $a["port"] . "/admin.cgi?pass=" . $a["password"] . "&mode=updinfo&song=" . urlencode($content));
  242. debug("Shoutcast metadata debug: RESPONSE = $response"); // Expected: nothing since the actual status is in the header. Logic? No, just laziness. ._.
  243. } else {
  244. // Simple http request
  245. $response = file_get_contents("http://" . (isset($a["username"]) ? $a["username"] . ":" : "") . $a["password"] . "@" . $a["host"] . ":" . $a["port"] . "/admin/metadata?mount=" . $a["mountpoint"] . "&mode=updinfo&song=" . urlencode($content));
  246. debug("Icecast2 metadata debug: RESPONSE = $response"); // Expected: XML code printing that the action was successful (1).
  247. }
  248. $a["metadata"] = $content;
  249. }
  250. }
  251. }