Use advanced transcoding that streams while transcoding
authorChristian Weiske <cweiske@cweiske.de>
Fri, 27 Nov 2015 20:00:54 +0000 (21:00 +0100)
committerChristian Weiske <cweiske@cweiske.de>
Fri, 27 Nov 2015 20:00:54 +0000 (21:00 +0100)
src/header.php [new file with mode: 0644]
src/mediatomb.php
www/index.php
www/transcode-cache.php [new file with mode: 0644]
www/transcode-nocache.php [new file with mode: 0644]

diff --git a/src/header.php b/src/header.php
new file mode 100644 (file)
index 0000000..8122384
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+set_include_path(get_include_path() . PATH_SEPARATOR . __DIR__ . '/../src/');
+$fullUri = $_SERVER['REQUEST_URI'];
+if (isset($_SERVER['REDIRECT_URL'])) {
+    $path    = $_SERVER['REDIRECT_URL'];
+} else {
+    $path = '/';
+}
+$dataDir = __DIR__ . '/../data/';
+$varDir  = realpath(__DIR__ . '/../var') . '/';
+$cacheDir = __DIR__ . '/../www/cache/';
+$host1 = 'http://radio567.vtuner.com/';
+$host2 = 'http://radio5672.vtuner.com/';
+if ($_SERVER['HTTP_HOST'] !== '') {
+    $host1 = 'http://' . $_SERVER['HTTP_HOST'] . '/';
+    $host2 = 'http://' . $_SERVER['HTTP_HOST'] . '/';
+}
+$cacheDirUrl = $host1 . 'cache/';
+$cfgFile = $dataDir . 'config.php';
+if (file_exists($cfgFile)) {
+    include $cfgFile;
+}
+?>
index 11ffe5a..e582a46 100644 (file)
@@ -26,9 +26,8 @@ function handleRequestMediatomb($fullPath, $prefix)
             $itemUrl = $item->url;
             if ($di->mimetype !== 'audio/mpeg') {
                 //noxon iRadio cube does not want to play .ogg files
-                $itemUrl = $host1 . 'transcode'
-                    . '?mtParentId=' . $container->id
-                    . '&mtItemTitle=' . urlencode($item->title);
+                $itemUrl = $host1 . 'transcode-nocache.php'
+                    . '?url=' . urlencode($itemUrl);
             }
             $listItems[] = getEpisodeItem(
                 $item->title,
@@ -44,66 +43,4 @@ function handleRequestMediatomb($fullPath, $prefix)
 
     sendListItems($listItems);
 }
-
-function transcodeMediatombItem($parentId, $title)
-{
-    global $mediatomb, $host1, $cacheDir, $cacheDirUrl;
-
-    if (!is_writable($cacheDir)) {
-        sendMessage('Cache dir not writable');
-        return;
-    }
-
-    extract($mediatomb);
-    try {
-        $smt = new Services_MediaTomb($user, $pass, $host, $port);
-        $item = $smt->getSingleItem((int) $parentId, $title, false);
-
-        $filename = $item->id . '.mp3';
-        $cacheFilePath = $cacheDir . $filename;
-        if (!file_exists($cacheFilePath)) {
-            transcodeUrlToMp3($item->url, $cacheFilePath);
-        }
-        if (!file_exists($cacheFilePath)) {
-            sendMessage('Error: No mp3 file found');
-            return;
-        }
-        $cacheFileUrl = $cacheDirUrl . $filename;
-        header('Location: ' . $cacheFileUrl);
-    } catch (Exception $e) {
-        sendMessage('Mediatomb error: ' . $e->getMessage());
-    }
-}
-
-function transcodeUrlToMp3($url, $mp3CacheFilePath)
-{
-    $tmpfile = tempnam(sys_get_temp_dir(), 'transcode');
-    exec(
-        'wget --quiet '
-        . escapeshellarg($url)
-        . ' -O ' . escapeshellarg($tmpfile),
-        $output,
-        $retval
-    );
-    if ($retval !== 0) {
-        throw new Exception('Error downloading URL');
-    }
-
-    exec(
-        'ffmpeg'
-        . ' -i ' . escapeshellarg($tmpfile)
-        . ' ' . escapeshellarg($mp3CacheFilePath)
-        . ' 2>&1',
-        $output,
-        $retval
-    );
-    unlink($tmpfile);
-    if ($retval !== 0) {
-        if (file_exists($mp3CacheFilePath)) {
-            unlink($mp3CacheFilePath);
-        }
-        //var_dump($tmpfile, $output);
-        throw new Exception('Error transcoding file');
-    }
-}
 ?>
index 65f6715..db6f8d8 100644 (file)
@@ -1,25 +1,5 @@
 <?php
-set_include_path(get_include_path() . PATH_SEPARATOR . __DIR__ . '/../src/');
-$fullUri = $_SERVER['REQUEST_URI'];
-if (isset($_SERVER['REDIRECT_URL'])) {
-    $path    = $_SERVER['REDIRECT_URL'];
-} else {
-    $path = '/';
-}
-$dataDir = __DIR__ . '/../data/';
-$varDir  = realpath(__DIR__ . '/../var') . '/';
-$cacheDir = __DIR__ . '/../www/cache/';
-$host1 = 'http://radio567.vtuner.com/';
-$host2 = 'http://radio5672.vtuner.com/';
-if ($_SERVER['HTTP_HOST'] !== '') {
-    $host1 = 'http://' . $_SERVER['HTTP_HOST'] . '/';
-    $host2 = 'http://' . $_SERVER['HTTP_HOST'] . '/';
-}
-$cacheDirUrl = $host1 . 'cache/';
-$cfgFile = $dataDir . 'config.php';
-if (file_exists($cfgFile)) {
-    include $cfgFile;
-}
+require_once __DIR__ . '/../src/header.php';
 
 if (strtolower($fullUri) == '/setupapp/radio567/asp/browsexpa/loginxml.asp?token=0'
     || $fullUri == '/RadioNativeLogin.php'
@@ -43,10 +23,6 @@ if (strtolower($fullUri) == '/setupapp/radio567/asp/browsexpa/loginxml.asp?token
 } else if ($path == '/RadioNativeFavorites.php') {
     //Favorites, defined via web interface
     sendMessage('Unsupported');
-} else if ($path == '/transcode') {
-    require_once 'mediatomb.php';
-    transcodeMediatombItem($_GET['mtParentId'], $_GET['mtItemTitle']);
-    exit();
 }
 
 handleRequest(ltrim($path, '/'));
diff --git a/www/transcode-cache.php b/www/transcode-cache.php
new file mode 100644 (file)
index 0000000..3525924
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Transcode audio file URLs to .mp3, cache them and stream them while
+ * transcoding is in progress.
+ */
+require_once __DIR__ . '/../src/header.php';
+
+if (!isset($_GET['url'])) {
+    errorOut('url parameter missing');
+}
+$parts = parse_url($_GET['url']);
+if ($parts === false || !isset($parts['scheme'])) {
+    errorOut('Invalid URL');
+}
+if ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https') {
+    errorOut('URL is neither http nor https');
+}
+$url = $_GET['url'];
+
+if (!is_dir($cacheDir)) {
+    errorOut('Cache dir does not exist');
+}
+if (!is_writable($cacheDir)) {
+    errorOut('Cache dir not writable');
+}
+
+$cacheFileName = str_replace(array(':', '/'), '-', $url) . '.mp3';
+$cacheFilePath = $cacheDir . $cacheFileName;
+
+if (file_exists($cacheFilePath)) {
+    header('HTTP/1.0 302 Moved Temporarily');
+    header('Location: ' . $cacheDirUrl . urlencode($cacheFileName));
+    exit(1);
+}
+
+$cmd = 'ffmpeg'
+    . ' -loglevel error'
+    . ' -i ' . escapeshellarg($url)
+    . ' -f mp3'
+    . ' -';
+
+$descriptorspec = array(
+    1 => array('pipe', 'w'),// stdout is a pipe that the child will write to
+    2 => array('pipe', 'w')//stderr
+);
+
+register_shutdown_function('shutdown');
+
+$process = proc_open($cmd, $descriptorspec, $pipes);
+if (is_resource($process)) {
+    $tmpCacheFile = tempnam(sys_get_temp_dir(), 'transcode-cache-');
+    $cacheHdl = fopen($tmpCacheFile, 'wb');
+    header('Content-type: audio/mpeg');
+    while ($data = fread($pipes[1], 1000)) {
+        //write cache
+        fwrite($cacheHdl, $data);
+        //output to browser
+        echo $data;
+        //TODO: maybe flush() and ob_flush();
+    }
+
+    $errors = stream_get_contents($pipes[2]);
+    fclose($pipes[1]);
+    fclose($pipes[2]);
+    $retval = proc_close($process);
+
+    fclose($cacheHdl);
+    if ($retval === 0) {
+        rename($tmpCacheFile, $cacheFilePath);
+    } else {
+        header('HTTP/1.0 500 Internal Server Error');
+        header('Content-type: text/plain');
+        echo "Error transcoding\n";
+        echo $errors . "\n";
+        unlink($tmpCacheFile);
+    }
+}
+
+function shutdown()
+{
+    global $process, $pipes, $tmpCacheFile;
+
+    if (connection_aborted()) {
+        //end ffmpeg and clean temp file
+        fclose($pipes[1]);
+        fclose($pipes[2]);
+        proc_terminate($process);
+        unlink($tmpCacheFile);
+    }
+}
+
+function errorOut($msg)
+{
+    header('HTTP/1.0 400 Bad request');
+    header('Content-type: text/plain');
+    echo $msg . "\n";
+    exit(1);
+}
+?>
diff --git a/www/transcode-nocache.php b/www/transcode-nocache.php
new file mode 100644 (file)
index 0000000..eae5573
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Transcode audio file URLs to .mp3 and stream it while
+ * transcoding is in progress.
+ */
+//require_once __DIR__ . '/../src/header.php';
+
+if (!isset($_GET['url'])) {
+    errorOut('url parameter missing');
+}
+$parts = parse_url($_GET['url']);
+if ($parts === false || !isset($parts['scheme'])) {
+    errorOut('Invalid URL');
+}
+if ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https') {
+    errorOut('URL is neither http nor https');
+}
+$url = $_GET['url'];
+
+$cmd = 'ffmpeg'
+    . ' -loglevel error'
+    . ' -i ' . escapeshellarg($url)
+    . ' -f mp3'
+    . ' -';
+
+$descriptorspec = array(
+    1 => array('pipe', 'w'),// stdout is a pipe that the child will write to
+    2 => array('pipe', 'w')//stderr
+);
+
+register_shutdown_function('shutdown');
+
+$process = proc_open($cmd, $descriptorspec, $pipes);
+if (is_resource($process)) {
+    header('Content-type: audio/mpeg');
+    while ($data = fread($pipes[1], 10000)) {
+        //output to browser
+        echo $data;
+        //TODO: maybe flush() and ob_flush();
+    }
+
+    $errors = stream_get_contents($pipes[2]);
+    fclose($pipes[1]);
+    fclose($pipes[2]);
+    $retval = proc_close($process);
+
+    if ($retval !== 0) {
+        header('HTTP/1.0 500 Internal Server Error');
+        header('Content-type: text/plain');
+        echo "Error transcoding\n";
+        echo $errors . "\n";
+    }
+}
+
+function shutdown()
+{
+    global $process, $pipes;
+
+    if (connection_aborted()) {
+        //end ffmpeg and clean temp file
+        fclose($pipes[1]);
+        fclose($pipes[2]);
+        proc_terminate($process);
+    }
+}
+
+function errorOut($msg)
+{
+    header('HTTP/1.0 400 Bad request');
+    header('Content-type: text/plain');
+    echo $msg . "\n";
+    exit(1);
+}
+?>