<?php

/**
 * @author Gernot Walzl
 */
class FileSystemObject {

    function read_dirs($path='.') {
        $result = null;
        if ($handle = opendir(dirname(__FILE__).'/'.$path)) {
            $result = array();
            while (false !== ($entry = readdir($handle))) {
                if (substr($entry, 0, 1) != '.') {
                    if (is_dir($path.'/'.$entry)) {
                        array_push($result, $path.'/'.$entry);
                    }
                }
            }
            closedir($handle);
            sort($result);
        }
        return $result;
    }

    function is_good_filename($filename, $exts=array()) {
        $result = false;
        if (substr($filename, 0, 1) != '.') {
            if (empty($exts)) {
                $result = true;
            } else {
                foreach ($exts as $ext) {
                    if (substr($filename, -strlen($ext)) === $ext) {
                        $result = true;
                        break;
                    }
                }
            }
        }
        return $result;
    }

    function read_files($path='.', $exts=array()) {
        $result = null;
        if ($handle = opendir(dirname(__FILE__).'/'.$path)) {
            $result = array();
            while (false !== ($file = readdir($handle))) {
                if ($this->is_good_filename($file, $exts)) {
                    if (is_file($path.'/'.$file) &&
                            is_readable($path.'/'.$file)) {
                        array_push($result, $path.'/'.$file);
                    }
                }
            }
            closedir($handle);
            sort($result);
        }
        return $result;
    }

}


/**
 * @author Gernot Walzl
 */
class CameraHTML {

    protected $fso = null;

    function __construct($fso) {
        $this->fso = $fso;
    }

    function println($msg) {
        print($msg."\n");
    }

    function print_head() {
        $this->println('<!DOCTYPE html>');
        $this->println('<html lang="en">');
        $this->println('<head>');
        $this->println('<meta charset="utf-8" />');
        $this->println('<title>Camera</title>');
        $this->println('<meta name="viewport" '.
                'content="width=device-width, initial-scale=1.0" />');
        $stylesheet = 'style.css';
        $href = $stylesheet.'?mtime='.filemtime($stylesheet);
        $this->println('<link rel="stylesheet" href="'.$href.'" />');
        $this->println('</head>');
        $this->println('<body>');
    }

    function print_dirs() {
        $dirs = $this->fso->read_dirs('rec');
        if (!empty($dirs)) {
            $this->println('<ul>');
            for ($cnt = count($dirs)-1; $cnt >= 0; $cnt--) {
                $dir = $dirs[$cnt];
                $this->println('<li>');
                $this->println('<a href="'.$_SERVER['PHP_SELF'].
                        '?dir='.urlencode($dir).'">'.
                        basename($dir).'</a>');
                $this->println('</li>');
            }
            $this->println('</ul>');
        }
    }

    function get_time_from_name($filepath) {
        $filename = basename($filepath);
        $hour = substr($filename, 11, 2);
        $minute = substr($filename, 13, 2);
        $second = substr($filename, 15, 2);
        return $hour.':'.$minute.':'.$second;
    }

    function print_mp4_files($path='.') {
        $exts = array('mp4');
        $files = $this->fso->read_files($path, $exts);
        if (!empty($files)) {
            $this->println('<table>');
            for ($cnt = count($files)-1; $cnt >= 0; $cnt--) {
                $file = $files[$cnt];
                $this->println('<tr>');
                $this->println('<td>'.basename($file).'</td>');
                $this->println('<td><a href="'.$file.'">Play</a></td>');
                $this->println('<td><a href="'.$file.'" download>Download</a></td>');
                $this->println('</tr>');
            }
            $this->println('</table>');
        }
    }

    function print_mjpg_files($path='.') {
        $exts = array('avi', 'mkv', 'mjpg');
        $files = $this->fso->read_files($path, $exts);
        if (!empty($files)) {
            $this->println('<table>');
            for ($cnt = count($files)-1; $cnt >= 0; $cnt--) {
                $file = $files[$cnt];
                $this->println('<tr>');
                $this->println('<td>'.basename($file).'</td>');
                $this->println('<td><a href="'.$_SERVER['PHP_SELF'].
                        '?play='.urlencode($file).'&fps=1">Play&nbsp;1fps</a></td>');
                $this->println('<td><a href="'.$_SERVER['PHP_SELF'].
                        '?play='.urlencode($file).'&fps=5">Play&nbsp;5fps</a></td>');
                $this->println('<td><a href="'.$_SERVER['PHP_SELF'].
                        '?play='.urlencode($file).'&fps=25">Play&nbsp;25fps</a></td>');
                $this->println('<td><a href="'.$file.'" download>Download</a></td>');
                $this->println('</tr>');
            }
            $this->println('</table>');
        }
    }

    function print_foot() {
        $this->println('</body>');
    }

}


/**
 * @author Gernot Walzl
 */
class MJPEGStreamer {

    function send_jpeg($data) {
        print("--jpgboundary\r\n");
        print("Content-Type: image/jpeg\r\n");
        print("Content-Length: ".strlen($data)."\r\n");
        print("\r\n");
        print($data);
        print("\r\n");
        flush();
    }

    function stream_mjpeg($path, $fps=10) {
        set_time_limit(0);
        header('Content-Type: multipart/x-mixed-replace; boundary=jpgboundary');

        $frametime_us = 1000000/$fps;

        $jpeg_begin = hex2bin('ffd8');
        $jpeg_end = hex2bin('ffd9');
        $jpeg_content = false;
        $jpeg_data = '';

        if ($handle = fopen($path, 'rb')) {
            while (!feof($handle)) {
                $contents = fread($handle, 4096);
                if ($jpeg_content) {
                    $pos_jpeg_stop = strpos($contents, $jpeg_end);
                    if ($pos_jpeg_stop) {
                        $jpeg_data .= substr($contents, 0, $pos_jpeg_stop+2);
                        $jpeg_content = false;
                        $this->send_jpeg($jpeg_data);
                        usleep($frametime_us);
                    } else {
                        $jpeg_data .= $contents;
                    }
                }
                if (!$jpeg_content) {
                    $pos_jpeg_start = strpos($contents, $jpeg_begin);
                    if ($pos_jpeg_start) {
                        $jpeg_content = true;
                        $jpeg_data = substr($contents, $pos_jpeg_start);
                    }
                }
            }
            fclose($handle);
        }
    }

}


if (!empty($_GET['play'])) {
    $file = stripslashes(urldecode($_GET['play']));
    if (!stristr($file, '..') &&
            file_exists(dirname(__FILE__).'/'.$file)) {
        $fps = 10;
        if (!empty($_GET['fps'])) {
            $fps = intval($_GET['fps']);
        }
        $streamer = new MJPEGStreamer();
        $streamer->stream_mjpeg($file, $fps);
    }
} else {
    $fso = new FileSystemObject();
    $html = new CameraHTML($fso);
    $html->print_head();
    $html->println('<header>');
    $html->println('<h1>Camera</h1>');
    $html->println('<p><a href="http://'.$_SERVER['SERVER_ADDR'].':8000/cam.mjpg">Live View</a></p>');
    $html->println('</header>');
    $html->println('<nav>');
    $html->println('<h2>Recordings</h2>');
    $html->print_dirs();
    $html->println('</nav>');
    $html->println('<main>');
    if (!empty($_GET['dir'])) {
        $dir = stripslashes(urldecode($_GET['dir']));
        if (!stristr($dir, '..') &&
                file_exists(dirname(__FILE__).'/'.$dir))  {
            $html->println('<div id="files">');
            $html->println('<h3>'.basename($dir).'</h3>');
            $html->print_mp4_files($dir);
            $html->print_mjpg_files($dir);
            $html->println('</div>');
        }
    }
    $html->println('</main>');
    $html->print_foot();
}

?>