1373 lines
51 KiB
PHP
1373 lines
51 KiB
PHP
<?php
|
|
ini_set('memory_limit', '-1');
|
|
ini_set("log_errors", 1);
|
|
ini_set("error_log", "myphp-error.log");
|
|
ini_set('display_errors', 1);
|
|
set_time_limit(0);
|
|
error_reporting(E_ERROR);
|
|
|
|
include "_db.php";
|
|
try {
|
|
$db = new PDO('mysql:host=' . $DBHost . ';dbname=' . $DBName . ';charset=utf8', $DBUser, $DBPass);
|
|
} catch (PDOException $e) {
|
|
print "Error!: " . $e->getMessage() . "<br/>";
|
|
die();
|
|
}
|
|
|
|
function ReadConfig()
|
|
{
|
|
global $db;
|
|
$sql = "select * from config";
|
|
$st = $db->prepare($sql);
|
|
$st->execute();
|
|
$data = $st->fetchAll();
|
|
return $data;
|
|
}
|
|
function GetConfig($Config, $ConfigName)
|
|
{
|
|
for ($i = 0; $i < count($Config); $i++) {
|
|
if ($Config[$i]["ConfigName"] == $ConfigName) {
|
|
return $Config[$i]["ConfigValue"];
|
|
}
|
|
}
|
|
}
|
|
function GetChannel($ChID)
|
|
{
|
|
global $db;
|
|
$sql = "select ID, ChannelName, Manifest, PID,
|
|
SegmentJoiner, PlaylistLimit, URLListLimit, DownloadUseragent, AudioID, VideoID, Output, UDPIP
|
|
from channels where ID=:ID";
|
|
$st = $db->prepare($sql);
|
|
$st->bindParam(":ID", $ChID);
|
|
$st->execute();
|
|
$line = $st->fetch();
|
|
$line["AudioIDs"] = explode(",", $line["AudioID"]);
|
|
|
|
$keySql = "select * from channel_keys where ChannelID=:ID";
|
|
$st = $db->prepare($keySql);
|
|
$st->bindParam(":ID", $ChID);
|
|
$st->execute();
|
|
$line["Keys"] = $st->fetchAll();
|
|
|
|
$headersSql = "select Value from channel_headers where ChannelID=:ID";
|
|
$st = $db->prepare($headersSql);
|
|
$st->bindParam(":ID", $ChID);
|
|
$st->execute();
|
|
$line["CustomHeaders"] = $st->fetchAll();
|
|
|
|
if ($line["ID"] != "") {
|
|
return $line;
|
|
} else {
|
|
echo "channel id not found";
|
|
die();
|
|
}
|
|
}
|
|
function UpdateChanStatus($ChID, $Status = "Downloading")
|
|
{
|
|
global $db;
|
|
static $RunOnlyOnce;
|
|
if (!$RunOnlyOnce) {
|
|
$RunOnlyOnce = 1;
|
|
$PID = intval(getmypid());
|
|
$sql = "update channels set Status='$Status', StartTime='" . date("Y-m-d H:i:s") . "', PID=$PID where ID=$ChID";
|
|
$db->exec($sql);
|
|
}
|
|
}
|
|
function UpdateChanStatus2($ChID, $Status = "Error")
|
|
{
|
|
global $db;
|
|
$PID = intval(getmypid());
|
|
$sql = "update channels set Status='$Status', StartTime='" . date("Y-m-d H:i:s") . "', PID=$PID where ID=$ChID";
|
|
$db->exec($sql);
|
|
}
|
|
function UpdateFPID($ChID, $FPID)
|
|
{
|
|
global $db;
|
|
$sql = "update channels set FPID=:FPID where ID=:ID";
|
|
$st = $db->prepare($sql);
|
|
$st->bindParam(":FPID", $FPID);
|
|
$st->bindParam(":ID", $ChID);
|
|
$st->execute();
|
|
}
|
|
|
|
function InitiateFolders($ChName, $WorkPath)
|
|
{
|
|
mkdir("tmp", 777, true);
|
|
mkdir($WorkPath, 777, true);
|
|
mkdir($WorkPath . "/" . $ChName, 777, true);
|
|
mkdir($WorkPath . "/" . $ChName . "/seg", 777, true);
|
|
mkdir($WorkPath . "/" . $ChName . "/stream", 777, true);
|
|
mkdir($WorkPath . "/" . $ChName . "/ts", 777, true);
|
|
mkdir($WorkPath . "/" . $ChName . "/hls", 777, true);
|
|
mkdir($WorkPath . "/" . $ChName . "/log", 777, true);
|
|
mkdir($WorkPath . "/" . $ChName . "/aria", 777, true);
|
|
if (!file_exists($WorkPath . "/" . $ChName . "/cache")) {
|
|
mkdir($WorkPath . "/" . $ChName . "/cache", 777, true);
|
|
}
|
|
|
|
array_map('unlink', array_filter((array) glob("tmp/*")));
|
|
array_map('unlink', array_filter((array) glob($WorkPath . "/" . $ChName . "/seg/*")));
|
|
array_map('unlink', array_filter((array) glob($WorkPath . "/" . $ChName . "/stream/*")));
|
|
array_map('unlink', array_filter((array) glob($WorkPath . "/" . $ChName . "/ts/*")));
|
|
array_map('unlink', array_filter((array) glob($WorkPath . "/" . $ChName . "/log/*")));
|
|
array_map('unlink', array_filter((array) glob($WorkPath . "/" . $ChName . "/hls/*")));
|
|
array_map('unlink', array_filter((array) glob($WorkPath . "/" . $ChName . "/aria/*")));
|
|
array_map('unlink', array_filter((array) glob($WorkPath . "/" . $ChName . "/*")));
|
|
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
|
} else {
|
|
exec("sudo chmod -R 777 " . $WorkPath);
|
|
exec("sudo chown -R www-data:www-data " . $WorkPath);
|
|
exec("sudo chmod -R 777 " . $WorkPath . "/" . $ChName);
|
|
exec("sudo chown -R www-data:www-data " . $WorkPath . "/" . $ChName);
|
|
}
|
|
}
|
|
function LoadMPD($mpd_url, $UseProxy, $Proxy, $Useragent, $customHeaders = [])
|
|
{
|
|
$data = Download($mpd_url, $UseProxy, $Proxy, $Useragent, $customHeaders);
|
|
if ($data) {
|
|
$loaded = simplexml_load_string($data);
|
|
if ($loaded) {
|
|
$dom_sxe1 = dom_import_simplexml($loaded);
|
|
$dom_doc = new DOMDocument('1.0');
|
|
$dom_sxe = $dom_doc->importNode($dom_sxe1, true);
|
|
$dom_doc->appendChild($dom_sxe);
|
|
$main_element_nodes = $dom_doc->getElementsByTagName('MPD');
|
|
return $main_element_nodes->item(0);
|
|
} else {
|
|
return null;
|
|
}
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
function ParseMPD_XML($mpd_xml)
|
|
{
|
|
return extract_features($mpd_xml);
|
|
}
|
|
function extract_features($xml)
|
|
{
|
|
$array = array();
|
|
$attributes = $xml->attributes;
|
|
$children = $xml->childNodes;
|
|
|
|
foreach ($attributes as $attribute) {
|
|
$array[$attribute->nodeName] = $attribute->nodeValue;
|
|
}
|
|
|
|
foreach ($children as $child) {
|
|
if (is_element($child)) {
|
|
$array[$child->nodeName][] = extract_features($child);
|
|
}
|
|
|
|
if ($child->nodeName == 'BaseURL') {
|
|
$array['BaseURL'][sizeof($array['BaseURL']) - 1]['anyURI'] = $child->firstChild->nodeValue;
|
|
}
|
|
|
|
}
|
|
return $array;
|
|
}
|
|
function is_element($element)
|
|
{
|
|
if (!empty($element->nodeName) && $element->nodeType == XML_ELEMENT_NODE) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
function current_period($mpd_features, $current_period)
|
|
{
|
|
$period_info = period_duration_info($mpd_features);
|
|
$AST = $mpd_features['availabilityStartTime'];
|
|
|
|
if ($mpd_features['type'] === 'static') {
|
|
$start = $period_info[0][$current_period];
|
|
$duration = $period_info[1][$current_period];
|
|
} elseif ($mpd_features['type'] === 'dynamic') {
|
|
if (sizeof($mpd_features['Period']) == 1) {
|
|
$current_period = 0;
|
|
$start = $period_info[0][0];
|
|
$duration = $period_info[1][0];
|
|
} else {
|
|
$now = time();
|
|
for ($p = 0; $p < sizeof($mpd_features['Period']); $p++) {
|
|
$whereami = $now - (strtotime($AST) + $period_info[0][$p]);
|
|
|
|
if ($whereami <= $period_info[1][$p]) {
|
|
$current_period = $p;
|
|
$start = $period_info[0][$p];
|
|
$duration = $period_info[1][$p];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return [$start, $duration];
|
|
}
|
|
function period_duration_info($mpd_features)
|
|
{
|
|
$periods = $mpd_features['Period'];
|
|
if (array_key_exists('mediaPresentationDuration', $mpd_features)) {
|
|
$mediapresentationduration = time_parsing($mpd_features['mediaPresentationDuration']);
|
|
} else {
|
|
$mediapresentationduration = 0;
|
|
}
|
|
|
|
$starts = array();
|
|
$durations = array();
|
|
for ($i = 0; $i < sizeof($periods); $i++) {
|
|
$period = $periods[$i];
|
|
|
|
$start = $period['start'];
|
|
$duration = $period['duration'];
|
|
if ($start == '') {
|
|
if ($i > 0) {
|
|
if ($durations[$i - 1] != '') {
|
|
$start = (float) ($starts[$i - 1] + $durations[$i - 1]);
|
|
} else {
|
|
if ($mpd_features['type'] == 'dynamic') {
|
|
//early available period
|
|
}
|
|
}
|
|
} else {
|
|
if ($mpd_features['type'] == 'static') {
|
|
$start = 0;
|
|
} elseif ($mpd_features['type'] == 'dynamic') {
|
|
//early available period
|
|
}
|
|
}
|
|
} else {
|
|
$start = time_parsing($start);
|
|
}
|
|
|
|
if ($duration == '') {
|
|
if ($i != sizeof($periods) - 1) {
|
|
$duration = time_parsing($periods[$i + 1]['start']) - $start;
|
|
} else {
|
|
$duration = $mediapresentationduration - $start;
|
|
}
|
|
} else {
|
|
$duration = time_parsing($duration);
|
|
}
|
|
|
|
$starts[] = $start;
|
|
$durations[] = min([$duration, 1800]);
|
|
}
|
|
|
|
return [$starts, $durations];
|
|
}
|
|
function time_parsing($var)
|
|
{
|
|
$y = str_replace("P", "", $var);
|
|
if (strpos($y, 'Y') !== false) { // Year
|
|
$Y = explode("Y", $y);
|
|
|
|
$y = substr($y, strpos($y, 'Y') + 1);
|
|
} else {
|
|
$Y[0] = 0;
|
|
}
|
|
|
|
if (strpos($y, 'M') !== false && strpos($y, 'M') < strpos($y, 'T')) { // Month
|
|
$Mo = explode("M", $y);
|
|
$y = substr($y, strpos($y, 'M') + 1);
|
|
} else {
|
|
$Mo[0] = 0;
|
|
}
|
|
|
|
if (strpos($y, 'W') !== false) { // Week
|
|
$W = explode("W", $y);
|
|
$y = substr($y, strpos($y, 'W') + 1);
|
|
} else {
|
|
$W[0] = 0;
|
|
}
|
|
|
|
if (strpos($y, 'D') !== false) { // Day
|
|
$D = explode("D", $y);
|
|
$y = substr($y, strpos($y, 'D') + 1);
|
|
} else {
|
|
$D[0] = 0;
|
|
}
|
|
|
|
$y = str_replace("T", "", $y);
|
|
if (strpos($y, 'H') !== false) { // Hour
|
|
$H = explode("H", $y);
|
|
$y = substr($y, strpos($y, 'H') + 1);
|
|
} else {
|
|
$H[0] = 0;
|
|
}
|
|
|
|
if (strpos($y, 'M') !== false) { // Minute
|
|
$M = explode("M", $y);
|
|
$y = substr($y, strpos($y, 'M') + 1);
|
|
} else {
|
|
$M[0] = 0;
|
|
}
|
|
|
|
$S = explode("S", $y); // Second
|
|
|
|
$duration = (intval($Y[0]) * 365 * 24 * 60 * 60) +
|
|
(intval($Mo[0]) * 30 * 24 * 60 * 60) +
|
|
(intval($W[0]) * 7 * 24 * 60 * 60) +
|
|
(intval($D[0]) * 24 * 60 * 60) +
|
|
(intval($H[0]) * 60 * 60) +
|
|
(intval($M[0]) * 60) +
|
|
intval($S[0]); // calculate durations in seconds
|
|
|
|
return $duration;
|
|
}
|
|
function process_base_url($mpd_url, $mpd_features, $current_period)
|
|
{
|
|
$base_url_used = false;
|
|
$mpd_base = $mpd_features['BaseURL'];
|
|
|
|
$period = $mpd_features['Period'][$current_period];
|
|
$period_base = $period['BaseURL'];
|
|
|
|
$adapts = $period['AdaptationSet'];
|
|
foreach ($adapts as $adapt) {
|
|
$adapt_base = $adapt['BaseURL'];
|
|
|
|
$reps = $adapt['Representation'];
|
|
foreach ($reps as $rep) {
|
|
$rep_base = $rep['BaseURL'];
|
|
|
|
if ($mpd_base || $period_base || $adapt_base || $rep_base) {
|
|
$base_url_used = true;
|
|
|
|
$dir = '';
|
|
$array = array($mpd_base, $period_base, $adapt_base, $rep_base);
|
|
foreach ($array as $item) {
|
|
if ($item) {
|
|
$base = $item[0]['anyURI'];
|
|
if (isAbsoluteURL($base)) {
|
|
$dir = $base;
|
|
} else {
|
|
$dir = $dir . $base;
|
|
}
|
|
|
|
}
|
|
|
|
$rep_url = $dir;
|
|
}
|
|
if (!isset($rep_url)) {
|
|
$rep_url = dirname($mpd_url) . '/';
|
|
}
|
|
|
|
} else {
|
|
$rep_url = dirname($mpd_url) . '/';
|
|
}
|
|
|
|
if (!isAbsoluteURL($rep_url)) {
|
|
$rep_url = dirname($mpd_url) . '/' . $rep_url;
|
|
}
|
|
|
|
$rep_urls[] = $rep_url;
|
|
}
|
|
$adapt_urls[] = $rep_urls;
|
|
$rep_urls = array();
|
|
}
|
|
return $adapt_urls;
|
|
}
|
|
function isAbsoluteURL($URL)
|
|
{
|
|
$parsedURL = parse_url($URL);
|
|
return $parsedURL['scheme'] && $parsedURL['host'];
|
|
}
|
|
function derive_segment_URLs($mpd_features, $current_period, $urls, $period_info)
|
|
{
|
|
$period = $mpd_features['Period'][$current_period];
|
|
$adaptation_sets = $period['AdaptationSet'];
|
|
$adapt_segment_urls = array();
|
|
foreach ($adaptation_sets as $i => $adaptation_set) {
|
|
$segment_template_high = get_segment_access($period['SegmentTemplate'], $adaptation_set['SegmentTemplate']);
|
|
$segment_base_high = get_segment_access($period['SegmentBase'], $adaptation_set['SegmentBase']);
|
|
|
|
$representations = $adaptation_set['Representation'];
|
|
$segment_access = array();
|
|
$segment_urls = array();
|
|
foreach ($representations as $j => $representation) {
|
|
|
|
$segment_template_low = get_segment_access($segment_template_high, $representation['SegmentTemplate']);
|
|
$segment_base_low = get_segment_access($segment_base_high, $representation['SegmentBase']);
|
|
|
|
if ($segment_template_low) {
|
|
$segment_access[] = $segment_template_low;
|
|
$segment_info = compute_timing($period_info[1], $segment_template_low[0], 'SegmentTemplate', $urls[$i][$j]);
|
|
$segment_urls[] = compute_URLs($mpd_features, $representation, $i, $j, $segment_template_low[0], $segment_info, $urls[$i][$j]);
|
|
} elseif ($segment_base_low) {
|
|
$segment_access[] = $segment_base_low;
|
|
$segment_urls[] = array($urls[$i][$j]);
|
|
} else {
|
|
$segment_access[] = '';
|
|
$segment_urls[] = array($urls[$i][$j]);
|
|
}
|
|
}
|
|
$adapt_segment_urls[] = $segment_urls;
|
|
$segment_info = array();
|
|
}
|
|
|
|
return $adapt_segment_urls;
|
|
}
|
|
function get_segment_access($high_level, $low_level)
|
|
{
|
|
$high_level_exists = !empty($high_level);
|
|
$low_level_exists = !empty($low_level);
|
|
|
|
if (!$high_level_exists && !$low_level_exists) {
|
|
return null;
|
|
} elseif ($high_level_exists && !$low_level_exists) {
|
|
return $high_level;
|
|
} elseif (!$high_level_exists && $low_level_exists) {
|
|
return $low_level;
|
|
} else {
|
|
return form_segment_access($high_level, $low_level);
|
|
}
|
|
|
|
}
|
|
function form_segment_access($high, $low)
|
|
{
|
|
foreach ($high as $index => $high_i) {
|
|
$low_i = $low[$index];
|
|
foreach ($high_i as $high_key => $high_value) {
|
|
if (!$low_i[$high_key]) {
|
|
$low_i[$high_key] = $high_value;
|
|
} else {
|
|
if (gettype($low_i[$high_key]) == 'array') {
|
|
$low_i[$high_key] = form_segment_access($high_i[$high_key], $low_i[$high_key]);
|
|
}
|
|
|
|
}
|
|
}
|
|
$low[$index] = $low_i;
|
|
}
|
|
return $low;
|
|
}
|
|
function compute_timing($presentationduration, $segment_access, $segment_access_type, $rep_base_url)
|
|
{
|
|
global $MyD;
|
|
$segment_timings = array();
|
|
$segmentno = 0;
|
|
$start = 0;
|
|
|
|
switch ($segment_access_type) {
|
|
case 'SegmentTemplate':
|
|
$duration = ($segment_access['duration'] != null) ? $segment_access['duration'] : 0;
|
|
$timescale = ($segment_access['timescale'] != null) ? $segment_access['timescale'] : 1;
|
|
$availabilityTimeOffset = ($segment_access['availabilityTimeOffset'] != null && $segment_access['availabilityTimeOffset'] != 'INF') ? $segment_access['availabilityTimeOffset'] : 0;
|
|
//$availabilityTimeOffset += ($rep_base_url['availabilityTimeOffset']) ? $rep_base_url['availabilityTimeOffset'] : 0;
|
|
$pto = ($segment_access['presentationTimeOffset'] != '') ? (int) ($segment_access['presentationTimeOffset']) / $timescale : 0;
|
|
|
|
if ($duration != 0) {
|
|
$duration /= $timescale;
|
|
$segmentno = ceil(($presentationduration - $start) / $duration);
|
|
}
|
|
|
|
$segment_timeline = $segment_access['SegmentTimeline'];
|
|
if ($segment_timeline != null) {
|
|
$S_array = $segment_timeline[0]['S'];
|
|
|
|
if ($S_array != null) {
|
|
$segment_time = ($S_array[0]['t']) ? $S_array[0]['t'] : 0;
|
|
$segment_time -= $pto;
|
|
$segment_time -= $availabilityTimeOffset;
|
|
|
|
foreach ($S_array as $index => $S) {
|
|
$d = $S['d'];
|
|
$MyD[] = ($d / $timescale);
|
|
|
|
if (array_key_exists("r", $S)) {
|
|
$r = ($S['r']) ? $S['r'] : 0;
|
|
} else {
|
|
$r = 0;
|
|
}
|
|
if (array_key_exists("t", $S)) {
|
|
$t = ($S['t']) ? $S['t'] : 0;
|
|
} else {
|
|
$t = 0;
|
|
}
|
|
$t -= $pto;
|
|
$t -= $availabilityTimeOffset;
|
|
|
|
if ($r == 0) {
|
|
$segment_timings[] = number_format((float) $segment_time, 0, '', '');
|
|
$segment_time += $d;
|
|
} elseif ($r < 0) {
|
|
if (!isset($S_array[$index + 1])) {
|
|
$end_time = $presentationduration * $timescale;
|
|
} else {
|
|
$end_time = ($S_array[$index + 1]['t']);
|
|
}
|
|
|
|
while ($segment_time < $end_time) {
|
|
$segment_timings[] = number_format((float) $segment_time, 0, '', '');
|
|
$segment_time += $d;
|
|
}
|
|
} else {
|
|
for ($st = 0; $st <= $r; $st++) {
|
|
$segment_timings[] = number_format((float) $segment_time, 0, '', '');
|
|
$segment_time += $d;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$startnumber = 1;
|
|
$segmentno = sizeof($segment_timings);
|
|
|
|
} else {
|
|
$index = 0;
|
|
$segment_time = $start - $pto - $availabilityTimeOffset;
|
|
while ($index < $segmentno) {
|
|
$segment_timings[] = $segment_time;
|
|
$segment_time += $duration;
|
|
$index++;
|
|
}
|
|
}
|
|
break;
|
|
case 'SegmentBase':
|
|
$segment_timings[] = $start;
|
|
$segmentno = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return [$segment_timings, $segmentno];
|
|
}
|
|
function compute_URLs($mpd_features, $representation, $adaptation_set_id, $representation_id, $segment_access, $segment_info, $rep_base_url)
|
|
{
|
|
$startNumber = ($segment_access['startNumber'] != null) ? $segment_access['startNumber'] : 1;
|
|
$initialization = $segment_access['initialization'];
|
|
$media = $segment_access['media'];
|
|
$bandwidth = $representation['bandwidth'];
|
|
$id = $representation['id'];
|
|
$segment_urls = array();
|
|
|
|
if ($initialization != null) {
|
|
$init = str_replace(array('$Bandwidth$', '$RepresentationID$'), array($bandwidth, $id), $initialization);
|
|
if (isAbsoluteURL($init)) {
|
|
$init_url = $init;
|
|
} else {
|
|
if (substr($rep_base_url, -1) == '/') {
|
|
$init_url = $rep_base_url . $init;
|
|
} else {
|
|
$init_url = $rep_base_url . "/" . $init;
|
|
}
|
|
|
|
}
|
|
$segment_urls[] = $init_url;
|
|
}
|
|
|
|
$index = 0;
|
|
$until = $segment_info[1];
|
|
|
|
if ($mpd_features['type'] == 'dynamic') {
|
|
//list($index, $until, $time1) = dynamic_number($adaptation_set_id, $representation_id, $segment_access, $segment_info[0], $segment_info[1]);
|
|
}
|
|
|
|
$error_info = '';
|
|
|
|
$x = 0;
|
|
while ($index < $until) {
|
|
$segmenturl = str_replace(array('$Bandwidth$', '$Number$', '$RepresentationID$', '$Time$'), array($bandwidth, $index + $startNumber, $id, $segment_info[0][$time1]), $media);
|
|
$x++;
|
|
$pos = strpos($segmenturl, '$Number');
|
|
if ($pos !== false) {
|
|
if (substr($segmenturl, $pos + strlen('$Number'), 1) === '%') {
|
|
$segmenturl = sprintf($segmenturl, $startNumber + $index);
|
|
$segmenturl = str_replace('$Number', '', $segmenturl);
|
|
$segmenturl = str_replace('$', '', $segmenturl);
|
|
$x++;
|
|
} else {
|
|
$error_info = "It cannot happen! the format should be either \$Number$ or \$Number%xd$!";
|
|
}
|
|
|
|
}
|
|
$pos = strpos($segmenturl, '$Time');
|
|
if ($pos !== false) {
|
|
if (substr($segmenturl, $pos + strlen('$Time'), 1) === '%') {
|
|
$segmenturl = sprintf($segmenturl, $segment_info[0][$index]);
|
|
$segmenturl = str_replace('$Time', '', $segmenturl);
|
|
$segmenturl = str_replace('$', '', $segmenturl);
|
|
} else {
|
|
$error_info = "It cannot happen! the format should be either \$Time$ or \$Time%xd$!";
|
|
}
|
|
|
|
}
|
|
|
|
if (!isAbsoluteURL($segmenturl)) {
|
|
if (substr($rep_base_url, -1) == '/') {
|
|
$segmenturl = $rep_base_url . $segmenturl;
|
|
} else {
|
|
$segmenturl = $rep_base_url . "/" . $segmenturl;
|
|
}
|
|
|
|
}
|
|
$segment_urls[] = $segmenturl;
|
|
$index++;
|
|
$time1++;
|
|
}
|
|
|
|
//if($error_info != '')
|
|
// error_log($error_info);
|
|
|
|
return $segment_urls;
|
|
}
|
|
function in_Timeline($url, $arr)
|
|
{
|
|
foreach ($arr as $item) {
|
|
//if($item["a"] == $url["a"] && $item["v"] == $url["v"]){
|
|
if ($item["v"] == $url["v"]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function JoinSegment($ChID, $ChName, $Keys, $aHeader, $aData, $vHeader, $vData, $DownloadIndex)
|
|
{
|
|
global $WorkPath, $BinPath;
|
|
global $FFMpegCMD;
|
|
global $DeleteEncryptedAfterDecrypt;
|
|
global $DeleteDecryptedAfterMerge;
|
|
global $PlaylistLimit;
|
|
global $db;
|
|
global $CheckKey;
|
|
|
|
$MyFFMpegCMD = $FFMpegCMD;
|
|
|
|
$Index = str_pad($DownloadIndex, 8, "0", STR_PAD_LEFT);
|
|
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
|
$Mp4Decrypt = $BinPath . '\\mp4decrypt.exe ';
|
|
$FFMpegBin = $BinPath . '\\ffmpeg.exe ';
|
|
$Redirect = " > nul";
|
|
} else {
|
|
$Mp4Decrypt = $BinPath . '/mp4decrypt ';
|
|
$FFMpegBin = 'ffmpeg ';
|
|
$Redirect = " 2>&1 & ";
|
|
}
|
|
|
|
$audio_ext = ".m4a";
|
|
$video_ext = ".mp4";
|
|
$OutExt = ".mp4";
|
|
$OutExt2 = ".ts";
|
|
|
|
$Merged_FileName = $WorkPath . "/" . $ChName . "/stream/$Index" . $OutExt2;
|
|
|
|
$map = "";
|
|
/** let mp4decrypt bruteforce the key */
|
|
$keyString = "";
|
|
foreach ($Keys as $key) {
|
|
$kid = $key["KID"];
|
|
$decKey = $key['Key'];
|
|
$keyString .= "--key $kid:$decKey ";
|
|
}
|
|
DoLog("Decrypting segment .... please wait .....");
|
|
for ($k = 0; $k < count($aData); $k++) {
|
|
$AudioEncFileName[] = $WorkPath . "/" . $ChName . "/seg/" . $Index . "-" . $k . "-enc" . $audio_ext;
|
|
$AudioDecFileName[] = $WorkPath . "/" . $ChName . "/seg/" . $Index . "-" . $k . "-dec" . $audio_ext;
|
|
$aSeg = null;
|
|
$aSeg = $aHeader . $aData[$k];
|
|
file_put_contents($AudioEncFileName[$k], $aSeg);
|
|
$dec = $Mp4Decrypt . $keyString . $AudioEncFileName[$k] . " " . $AudioDecFileName[$k] . " --show-progress " . $Redirect;
|
|
exec($dec);
|
|
$map .= " -map " . ($k + 1) . ":a ";
|
|
}
|
|
|
|
$VideoEncFileName = $WorkPath . "/" . $ChName . "/seg/" . $Index . "-enc" . $video_ext;
|
|
$VideoDecFileName = $WorkPath . "/" . $ChName . "/seg/" . $Index . "-dec" . $video_ext;
|
|
$vSeg = $vHeader . $vData;
|
|
file_put_contents($VideoEncFileName, $vSeg);
|
|
$dec = $Mp4Decrypt . $keyString . $VideoEncFileName . " " . $VideoDecFileName . " --show-progress " . $Redirect;
|
|
exec($dec);
|
|
|
|
$MyFFMpegCMD = str_replace("-i", "", $MyFFMpegCMD);
|
|
$MyFFMpegCMD = str_replace("[VIDEO]", " -i " . $VideoDecFileName, $MyFFMpegCMD);
|
|
for ($k = 0; $k < count($aData); $k++) {
|
|
$strAudioIn .= " -copyts -i " . $AudioDecFileName[$k] . " ";
|
|
}
|
|
$MyFFMpegCMD = str_replace("[AUDIO]", $strAudioIn, $MyFFMpegCMD);
|
|
$MyFFMpegCMD = str_replace("[OUTPUT]", $Merged_FileName, $MyFFMpegCMD);
|
|
$cmd = $FFMpegBin . " -copyts " . $MyFFMpegCMD . $Redirect;
|
|
|
|
//$cmd=$FFMpegBin." -hide_banner -start_at_zero -correct_ts_overflow 0 -avoid_negative_ts disabled -max_interleave_delta 0 -i $VideoDecFileName $strAudioIn -map 0:v $map -c:v copy -c:a copy $Merged_FileName";
|
|
$cmd = $FFMpegBin . " -hide_banner -probesize 10M -analyzeduration 10M -fflags +igndts -copyts -i $VideoDecFileName $strAudioIn -map 0:v $map -c:v copy -c:a aac -bsf:a aac_adtstoasc $Merged_FileName ";
|
|
echo $cmd;
|
|
$Res = null;
|
|
exec($cmd, $Res);
|
|
|
|
if ($CheckKey) {
|
|
$cmd = "ffmpeg -v error -i $Merged_FileName -f null - > $WorkPath/$ChName/log/checkkey.txt 2>&1";
|
|
exec($cmd);
|
|
$Err = file_get_contents("$WorkPath/$ChName/log/checkkey.txt");
|
|
if (strpos($Err, "error while decoding") === false) {
|
|
//ok
|
|
} else {
|
|
UpdateChanStatus2($ChID, "KeyError");
|
|
die();
|
|
}
|
|
}
|
|
|
|
$cmd = "ffprobe -v quiet -print_format json -show_streams -show_format $Merged_FileName > a.json";
|
|
exec($cmd);
|
|
$v = json_decode(file_get_contents("a.json"), true);
|
|
unlink("a.json");
|
|
|
|
$info["vcodec"] = $v["streams"][0]["codec_name"];
|
|
$info["width"] = $v["streams"][0]["width"];
|
|
$info["height"] = $v["streams"][0]["height"];
|
|
$info["ratio"] = $v["streams"][0]["display_aspect_ratio"];
|
|
$info["framerate"] = $v["streams"][0]["avg_frame_rate"];
|
|
$info["acodec"] = $v["streams"][1]["codec_name"];
|
|
$info["channels"] = $v["streams"][1]["channel_layout"];
|
|
$info["samplerate"] = $v["streams"][1]["sample_rate"];
|
|
$info["bitrate"] = $v["format"]["bit_rate"];
|
|
$data = json_encode($info);
|
|
if ($info["vcodec"]) {
|
|
$sql = "update channels set info='$data' where ID=$ChID";
|
|
$db->exec($sql);
|
|
}
|
|
|
|
if ($DeleteEncryptedAfterDecrypt) {
|
|
array_map('unlink', array_filter((array) $AudioEncFileName));
|
|
unlink($VideoEncFileName);
|
|
}
|
|
if ($DeleteDecryptedAfterMerge) {
|
|
array_map('unlink', array_filter((array) $AudioDecFileName));
|
|
unlink($VideoDecFileName);
|
|
}
|
|
|
|
UpdatePlaylist($ChName, $ChID, $map);
|
|
|
|
}
|
|
|
|
function UpdatePlaylist($ChName, $ChID, $map2)
|
|
{
|
|
global $WorkPath, $BinPath;
|
|
global $PlaylistLimit;
|
|
global $FFMpegPID;
|
|
global $db;
|
|
global $Output;
|
|
global $UDPIP;
|
|
|
|
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
|
$FFMpegBin = $BinPath . '\\ffmpeg.exe ';
|
|
} else {
|
|
$FFMpegBin = 'ffmpeg ';
|
|
}
|
|
$OutExt = ".ts";
|
|
$Folder = $WorkPath . "/" . $ChName . "/stream/";
|
|
$Path = $WorkPath . "/" . $ChName . "/stream/*" . $OutExt;
|
|
$Files = glob($Path);
|
|
if (count($Files) > $PlaylistLimit + 3) {
|
|
unlink($Files[0]);
|
|
}
|
|
$Files = glob($Path);
|
|
$seq = 0;
|
|
|
|
$m3u8files = "";
|
|
for ($i = 0; $i < count($Files); $i++) {
|
|
$file = str_replace($Folder, "", $Files[$i]);
|
|
if ($seq == 0) {
|
|
$seq = intval(str_replace($OutExt, "", $file));
|
|
}
|
|
|
|
$cmd = $FFMpegBin . " -hide_banner -i " . $Files[$i] . " > tmp.txt 2>&1";
|
|
exec($cmd);
|
|
$tmp = file("tmp.txt");
|
|
unlink("tmp.txt");
|
|
foreach ($tmp as $line) {
|
|
if (substr(trim($line), 0, 9) == "Duration:") {
|
|
$dur = explode(":", explode(",", $line)[0]);
|
|
}
|
|
}
|
|
$dur = $dur[count($dur) - 1];
|
|
if ($dur > $MaxDur) {
|
|
$MaxDur = number_format($dur, 2, '.', '');
|
|
}
|
|
|
|
$m3u8files .= PHP_EOL . "#EXTINF:$dur," . PHP_EOL . $file;
|
|
}
|
|
$FileHeader = ""
|
|
. "#EXTM3U" . PHP_EOL
|
|
. "#EXT-X-VERSION:3" . PHP_EOL
|
|
. "#EXT-X-MEDIA-SEQUENCE:" . ($seq + 2) . PHP_EOL
|
|
. "#EXT-X-ALLOW-CACHE:YES" . PHP_EOL
|
|
. "#EXT-X-TARGETDURATION:$MaxDur";
|
|
file_put_contents($WorkPath . "/" . $ChName . "/stream/index.m3u8", $FileHeader . $m3u8files);
|
|
|
|
if (count($Files) >= $PlaylistLimit - 1) {
|
|
if (!$FFMpegPID || !file_exists('/proc/' . $FFMpegPID)) {
|
|
if (!file_exists('/proc/' . $FFMpegPID) && $FFMpegPID > 0) {
|
|
$Msg = "Channel: " . $ChName . ", ID: " . $ChID . " restarted at: " . date("H:i A");
|
|
$sql = "insert into notification (Title, Msg, Sent, Status) values ('Downloader restart', '$Msg', now(), 'New')";
|
|
$db->exec($sql);
|
|
}
|
|
$cmd = 'ffmpeg ' .
|
|
'-start_at_zero ' .
|
|
'-correct_ts_overflow 0 ' .
|
|
'-avoid_negative_ts disabled ' .
|
|
'-max_interleave_delta 0 ' .
|
|
//'-copyts '.
|
|
'-re ' .
|
|
'-probesize 9000000 ' .
|
|
'-analyzeduration 9000000 ' .
|
|
'-i "' . $WorkPath . '/' . $ChName . '/stream/index.m3u8" ' .
|
|
'-map 0 ' .
|
|
'-vcodec copy ' .
|
|
'-scodec copy ' .
|
|
'-acodec copy ' .
|
|
'-f hls ' .
|
|
'-hls_time 10 ' .
|
|
'-hls_list_size 6 ' .
|
|
'-hls_allow_cache 1 ' .
|
|
'-hls_segment_type mpegts ' .
|
|
'-hls_flags append_list ' .
|
|
'-hls_flags omit_endlist ' .
|
|
'-hls_flags delete_segments ' .
|
|
'-hls_segment_filename "' . $WorkPath . '/' . $ChName . '/hls/seg%06d.ts" ' .
|
|
'"' . $WorkPath . '/' . $ChName . '/hls/index.m3u8" ' .
|
|
'> "' . $WorkPath . '/' . $ChName . '/log/ffmpeg.log" ' .
|
|
'2>&1 & echo $!;';
|
|
|
|
$FFMpegPID = exec($cmd, $res);
|
|
UpdateFPID($ChID, $FFMpegPID);
|
|
}
|
|
}
|
|
}
|
|
function Download($url, $UseProxy = 0, $Proxy = [], $Useragent = "", $customHeaders = [])
|
|
{
|
|
$ch = @curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, $url);
|
|
|
|
$head[] = "Connection: keep-alive";
|
|
$head[] = "Keep-Alive: 300";
|
|
$head[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
|
|
$head[] = "Accept-Language: en-us,en;q=0.5";
|
|
|
|
if ($customHeaders != null && count($customHeaders) > 0) {
|
|
foreach ($customHeaders as $customHeader) {
|
|
$head[] = $customHeader["Value"];
|
|
}
|
|
}
|
|
|
|
if ($UseProxy) {
|
|
$ProxyPassUser = $Proxy["User"] . ":" . $Proxy["Pass"];
|
|
curl_setopt($ch, CURLOPT_PROXY, $Proxy["URL"]);
|
|
curl_setopt($ch, CURLOPT_PROXYPORT, $Proxy["Port"]);
|
|
if ($ProxyPassUser != ":") {
|
|
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $ProxyPassUser);
|
|
curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
|
|
}
|
|
}
|
|
if ($Useragent) {
|
|
curl_setopt($ch, CURLOPT_USERAGENT, $Useragent);
|
|
}
|
|
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $head);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
|
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60);
|
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
|
$page = curl_exec($ch);
|
|
curl_close($ch);
|
|
return $page;
|
|
}
|
|
function DownloadRetry($url, $UseProxy = 0, $Proxy = [], $Useragent = "", $customHeaders = [], $maxRetries = 5)
|
|
{
|
|
for ($i = 0; $i < $maxRetries; $i++) {
|
|
$page = Download($url, $UseProxy, $Proxy, $Useragent, $customHeaders);
|
|
if ($page) {
|
|
return $page;
|
|
}
|
|
sleep(0.5);
|
|
}
|
|
return null;
|
|
}
|
|
function DoLog($Msg)
|
|
{
|
|
global $WorkPath, $ChName, $ScreenLog, $Config;
|
|
$Msg = date("Y-m-d H:i:s") . " " . $Msg . PHP_EOL;
|
|
if (substr(php_uname(), 0, 7) == "Windows") {
|
|
$LogFileName = $LogPath . "\\" . $ChName . "\\log\\php.log";
|
|
} else {
|
|
$LogFileName = $WorkPath . "/" . $ChName . "/log/php.log";
|
|
}
|
|
$MaxLogSize = intval(GetConfig($Config, "MaxLogSize"));
|
|
if ($MaxLogSize == 0) {
|
|
$MaxLogSize = 1024 * 1024;
|
|
}
|
|
|
|
if (filesize($LogFileName) > $MaxLogSize) {
|
|
$handle = fopen($LogFileName, "r");
|
|
$contents = fread($handle, filesize($LogFileName) - $MaxLogSize);
|
|
file_put_contents($LogFileName, $contents);
|
|
}
|
|
file_put_contents($LogFileName, $Msg, FILE_APPEND);
|
|
if ($ScreenLog) {
|
|
echo $Msg;
|
|
}
|
|
}
|
|
|
|
function DownloadList($List, $UseProxy = 0, $Proxy = [], $userAgent = "", $customHeaders = [])
|
|
{
|
|
global $WorkPath, $ChName;
|
|
$Folder = $WorkPath . "/" . $ChName;
|
|
|
|
$j = (count($List[0]["a"]) + 1) * count($List);
|
|
|
|
for ($i = 0; $i < count($List); $i++) {
|
|
for ($k = 0; $k < count($List[$i]["a"]); $k++) {
|
|
$a = $List[$i]["a"][$k];
|
|
$str .= $a . "\r\n out=$i.$k.a\r\n";
|
|
}
|
|
$v = $List[$i]["v"];
|
|
$str .= $v . "\r\n out=$i.v\r\n";
|
|
}
|
|
file_put_contents("list.txt", $str);
|
|
$customHeaderText = "";
|
|
foreach ($customHeaders as $header) {
|
|
$value = $header["Value"];
|
|
$customHeaderText .= "--header=\"$value\" ";
|
|
}
|
|
$userAgentText = "";
|
|
if ($userAgent != "") {
|
|
$userAgentText = "--user-agent=\"$userAgent\" ";
|
|
}
|
|
if ($UseProxy) {
|
|
$cmd = 'aria2c --continue=true -t 10 --check-certificate=false ' . $customHeaderText . ' ' . $userAgentText . '--http-proxy="http://' . $Proxy["User"] . ':' . $Proxy["Pass"] . '@' . $Proxy["URL"] . ':' . $Proxy["Port"] . '" -i list.txt --dir=' . $Folder . ' -j' . $j;
|
|
} else {
|
|
$cmd = 'aria2c --continue=true -t 10 --check-certificate=false ' . $customHeaderText . ' ' . $userAgentText . '-i list.txt --dir=' . $Folder . ' -j' . $j;
|
|
}
|
|
echo $cmd;
|
|
exec($cmd);
|
|
for ($i = 0; $i < count($List); $i++) {
|
|
for ($k = 0; $k < count($List[$i]["a"]); $k++) {
|
|
$tmp["a"][$k] .= file_get_contents("$Folder/$i.$k.a");
|
|
exec("sudo rm $Folder/$i.$k.a");
|
|
}
|
|
$tmp["v"] .= file_get_contents("$Folder/$i.v");
|
|
exec("sudo rm $Folder/$i.v");
|
|
}
|
|
return $tmp;
|
|
}
|
|
|
|
$mpd_features;
|
|
$current_period = 0;
|
|
$current_adaptation_set = 0;
|
|
$current_representation = 0;
|
|
$period_timing_info = [];
|
|
$MyD = [];
|
|
$FFMpegPID = null;
|
|
|
|
$Options = getopt(null, ["mode:", "chid::", "mpdurl::", "proxyurl::", "proxyport::", "proxyuser::", "proxypass::", "useragent::", "screenlog::", "checkkey::"]);
|
|
|
|
$RunMode = $Options["mode"]; //infoshort, infolong, testonly, download
|
|
$CheckKey = $Options["checkkey"];
|
|
$ChID = $Options["chid"];
|
|
$Mpd_Url = $Options["mpdurl"];
|
|
|
|
//command line proxy parameters overrides channel and system configurations
|
|
$Proxy["URL"] = $Options["proxyurl"];
|
|
$Proxy["Port"] = $Options["proxyport"];
|
|
$Proxy["User"] = $Options["proxyuser"];
|
|
$Proxy["Pass"] = $Options["proxypass"];
|
|
$Useragent = $Options["useragent"];
|
|
$UseProxy = $Proxy["URL"] != "" ? 1 : 0;
|
|
$ScreenLog = $Options["screenlog"];
|
|
|
|
$Config = ReadConfig();
|
|
|
|
$DeleteEncryptedAfterDecrypt = GetConfig($Config, "DeleteEncryptedAfterDecrypt");
|
|
$DeleteDecryptedAfterMerge = GetConfig($Config, "DeleteDecryptedAfterMerge");
|
|
$WorkPath = GetConfig($Config, "DownloadPath");
|
|
$BinPath = GetConfig($Config, "BinPath");
|
|
$SegmentJoiner = intval(GetConfig($Config, "SegmentJoiner"));
|
|
$URLListLimit = intval(GetConfig($Config, "URLListLimit"));
|
|
$PlaylistLimit = intval(GetConfig($Config, "PlaylistLimit"));
|
|
$FFMpegCMD = GetConfig($Config, "FFMpegCMD");
|
|
|
|
if ($Useragent == "") {
|
|
$Useragent = GetConfig($Config, "DownloadUseragent");
|
|
}
|
|
if ($ChID) {
|
|
$ChData = GetChannel($ChID);
|
|
$Output = $ChData["Output"];
|
|
$UDPIP = $ChData["UDPIP"];
|
|
|
|
$Mpd_Url = $ChData["Manifest"];
|
|
|
|
// $Key = $ChData["KID"].":".$ChData["Key"];
|
|
$Keys = $ChData["Keys"];
|
|
$CustomHeaders = $ChData["CustomHeaders"];
|
|
$ChName = str_replace(" ", "_", $ChData["ChannelName"]);
|
|
|
|
$Useragent = $ChData["DownloadUseragent"] ? $ChData["DownloadUseragent"] : $Useragent;
|
|
$SegmentJoiner = intval($ChData["SegmentJoiner"]) > 0 ? $ChData["SegmentJoiner"] : $SegmentJoiner;
|
|
$URLListLimit = intval($ChData["URLListLimit"]) >= 1 ? $ChData["URLListLimit"] : $PlaylistLimit;
|
|
$PlaylistLimit = intval($ChData["PlaylistLimit"]) >= 3 ? $ChData["PlaylistLimit"] : $PlaylistLimit;
|
|
|
|
if (!isset($Options["proxyurl"])) {
|
|
$UseProxy = intval($ChData["UseProxy"]) == 1;
|
|
if ($UseProxy) {
|
|
if ($ChData["ProxyURL"]) {
|
|
$Proxy["URL"] = $ChData["ProxyURL"];
|
|
$Proxy["Port"] = $ChData["ProxyPort"];
|
|
$Proxy["User"] = $ChData["ProxyUser"];
|
|
$Proxy["Pass"] = $ChData["ProxyPass"];
|
|
$Proxy["Useragent"] = $ChData["ProxyUseragent"];
|
|
} else {
|
|
$Proxy["URL"] = GetConfig($Config, "ProxyURL");
|
|
$Proxy["Port"] = GetConfig($Config, "ProxyPort");
|
|
$Proxy["User"] = GetConfig($Config, "ProxyUser");
|
|
$Proxy["Pass"] = GetConfig($Config, "ProxyPass");
|
|
}
|
|
}
|
|
}
|
|
InitiateFolders($ChName, $WorkPath);
|
|
ini_set("error_log", $WorkPath . "/" . $ChName . "/log/php_error.log");
|
|
}
|
|
if (!$Mpd_Url) {
|
|
echo "must provide valid mpd url";
|
|
die();
|
|
}
|
|
$DownloadIndex = 1;
|
|
$SegmentCounter = 0;
|
|
|
|
$aHeader = "";
|
|
$vHeader = "";
|
|
try {
|
|
Start:
|
|
$Xml_DOM = null;
|
|
$StartTime = time();
|
|
|
|
$Xml_DOM = LoadMPD($Mpd_Url, $UseProxy, $Proxy, $Useragent, $CustomHeaders);
|
|
if (!$Xml_DOM) {
|
|
echo "can not load mpd.";
|
|
die();
|
|
}
|
|
$mpd_features = ParseMPD_XML($Xml_DOM);
|
|
$UpdateInterval = time_parsing($mpd_features["minimumUpdatePeriod"]);
|
|
if (intval($UpdateInterval) == 0) {
|
|
$UpdateInterval = array_sum($MyD) / count($MyD);
|
|
}
|
|
|
|
if (intval($UpdateInterval) == 0) {
|
|
$UpdateInterval = 2.1;
|
|
}
|
|
|
|
$current_period = 0;
|
|
$period_info = current_period($mpd_features, $current_period);
|
|
$period_timing_info = $period_info;
|
|
|
|
$urls = process_base_url($Mpd_Url, $mpd_features, $current_period);
|
|
|
|
$segment_urls = derive_segment_URLs($mpd_features, $current_period, $urls, $period_info);
|
|
$period = $mpd_features['Period'][$current_period];
|
|
$adaptation_sets = $period['AdaptationSet'];
|
|
|
|
switch ($RunMode) {
|
|
case "infoshort":{
|
|
foreach ($adaptation_sets as $Adpt) {
|
|
$Reprs = $Adpt['Representation'];
|
|
foreach ($Reprs as $Rep) {
|
|
if ($Adpt["mimeType"] == "audio/mp4") {
|
|
$Rep["codecs"] = $Adpt["codecs"];
|
|
$Rep["lang"] = $Adpt["lang"];
|
|
$ARep[] = $Rep;
|
|
}
|
|
if ($Adpt["mimeType"] == "video/mp4") {
|
|
$Rep["maxwidth"] = $Adpt["maxWidth"];
|
|
$Rep["maxheight"] = $Adpt["maxHeight"];
|
|
$Rep["aframerate"] = $Adpt["frameRate"];
|
|
$VRep[] = $Rep;
|
|
}
|
|
}
|
|
}
|
|
foreach ($ARep as $ar) {
|
|
foreach ($VRep as $vr) {
|
|
if (!$vr["width"]) {
|
|
$vr["width"] = $vr["maxwidth"];
|
|
}
|
|
|
|
if (!$vr["height"]) {
|
|
$vr["height"] = $vr["maxheight"];
|
|
}
|
|
|
|
if (!$vr["frameRate"]) {
|
|
$vr["frameRate"] = $vr["aframerate"];
|
|
}
|
|
|
|
echo $ar["lang"] . "|" . $ar["id"] . "|" . $ar["bandwidth"] . "|" . $ar["codecs"] . "|" . $vr["id"] . "|" . $vr["bandwidth"] . "|" . $vr["codecs"] . "|" . $vr["width"] . "|" . $vr["height"] . "|" . $vr["frameRate"] . "\r\n";
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case "infojson":{
|
|
foreach ($adaptation_sets as $Adpt) {
|
|
$Reprs = $Adpt['Representation'];
|
|
foreach ($Reprs as $Rep) {
|
|
if ($Adpt["mimeType"] == "audio/mp4") {
|
|
$Rep["codecs"] = $Adpt["codecs"];
|
|
$Rep["lang"] = $Adpt["lang"];
|
|
$ARep[] = $Rep;
|
|
}
|
|
if ($Adpt["mimeType"] == "video/mp4") {
|
|
$Rep["maxwidth"] = $Adpt["maxWidth"];
|
|
$Rep["maxheight"] = $Adpt["maxHeight"];
|
|
$Rep["aframerate"] = $Adpt["frameRate"];
|
|
$VRep[] = $Rep;
|
|
}
|
|
}
|
|
}
|
|
foreach ($ARep as $ar) {
|
|
$Json["a"][] = $ar["id"];
|
|
}
|
|
foreach ($VRep as $vr) {
|
|
$Json["v"][] = $vr["id"];
|
|
}
|
|
echo json_encode($Json);
|
|
break;
|
|
}
|
|
case "infolong":{
|
|
foreach ($adaptation_sets as $Adpt) {
|
|
$Reprs = $Adpt['Representation'];
|
|
foreach ($Reprs as $Rep) {
|
|
if ($Adpt["mimeType"] == "audio/mp4") {
|
|
$ARep[] = $Rep;
|
|
}
|
|
|
|
if ($Adpt["mimeType"] == "video/mp4") {
|
|
$VRep[] = $Rep;
|
|
}
|
|
|
|
}
|
|
}
|
|
echo "Audio:\r\n";
|
|
foreach ($ARep as $r) {
|
|
echo " id -> " . $r["id"] . str_repeat(" ", 30 - strlen($r["id"])) . " -> bw: " . $r["bandwidth"] . " lang: " . $r["lang"] . "\r\n";
|
|
}
|
|
echo "Video:\r\n";
|
|
foreach ($VRep as $r) {
|
|
echo " id -> " . $r["id"] . str_repeat(" ", 30 - strlen($r["id"])) . " -> W: " . $r["width"] . " H: " . $r["height"] . "\r\n";
|
|
}
|
|
break;
|
|
}
|
|
case "testonly":{
|
|
foreach ($adaptation_sets as $Adpt) {
|
|
$Reprs = $Adpt['Representation'];
|
|
foreach ($Reprs as $Rep) {
|
|
if ($Adpt["mimeType"] == "audio/mp4") {$Rep["lang"] = $Adpt["lang"];
|
|
$ARep[] = $Rep;}
|
|
if ($Adpt["mimeType"] == "video/mp4") {
|
|
$VRep[] = $Rep;
|
|
}
|
|
|
|
}
|
|
}
|
|
echo "Audio:\r\n";
|
|
foreach ($ARep as $r) {
|
|
echo " id -> " . $r["id"] . str_repeat(" ", 30 - strlen($r["id"])) . " -> bw: " . $r["bandwidth"] . " lang: " . $r["lang"] . "\r\n";
|
|
}
|
|
echo "Video:\r\n";
|
|
foreach ($VRep as $r) {
|
|
echo " id -> " . $r["id"] . str_repeat(" ", 30 - strlen($r["id"])) . " -> W: " . $r["width"] . " H: " . $r["height"] . "\r\n";
|
|
}
|
|
$a_url = [];
|
|
$v_url = [];
|
|
$current_representation = 0;
|
|
$current_adaptation_set = 0;
|
|
while ($current_adaptation_set < sizeof($adaptation_sets)) {
|
|
$adaptation_set = $adaptation_sets[$current_adaptation_set];
|
|
$representations = $adaptation_set['Representation'];
|
|
while ($current_representation < sizeof($representations)) {
|
|
$representation = $representations[$current_representation];
|
|
$segment_url = $segment_urls[$current_adaptation_set][$current_representation];
|
|
$a_url = $segment_urls[$current_adaptation_set][$current_representation];
|
|
$v_url = $segment_urls[$current_adaptation_set][$current_representation];
|
|
break (2);
|
|
}
|
|
}
|
|
echo "\r\n";
|
|
echo "Audio TS Timeline: " . (count($a_url) - 1) . " segments\r\n";
|
|
echo "Video TS Timeline: " . (count($v_url) - 1) . " segments\r\n";
|
|
echo " Segments Average: " . number_format(array_sum($MyD) / count($MyD), 2, '.', '') . "/Seconds\r\n";
|
|
echo " Update Interval: " . number_format($UpdateInterval, 2, '.', '') . "/Seconds\r\n";
|
|
break;
|
|
}
|
|
case "download":{
|
|
DoLog("---------------- Round loggin start point ----------------");
|
|
DoLog("Downloading in progress .. update channel status");
|
|
UpdateChanStatus($ChID, "Downloading");
|
|
|
|
$a_url = [];
|
|
$v_url = [];
|
|
$current_representation = 0;
|
|
$current_adaptation_set = 0;
|
|
while ($current_adaptation_set < sizeof($adaptation_sets)) {
|
|
$adaptation_set = $adaptation_sets[$current_adaptation_set];
|
|
$representations = $adaptation_set['Representation'];
|
|
while ($current_representation < sizeof($representations)) {
|
|
$representation = $representations[$current_representation];
|
|
$segment_url = $segment_urls[$current_adaptation_set][$current_representation];
|
|
if (in_array($representation["id"], $ChData["AudioIDs"])) {
|
|
$a_url[] = $segment_urls[$current_adaptation_set][$current_representation];
|
|
}
|
|
if ($ChData["VideoID"] == $representation["id"]) {
|
|
$v_url = $segment_urls[$current_adaptation_set][$current_representation];
|
|
}
|
|
$current_representation++;
|
|
}
|
|
$current_representation = 0;
|
|
$current_adaptation_set++;
|
|
}
|
|
|
|
$aHeaderUrl = $a_url[0][0];
|
|
$vHeaderUrl = $v_url[0];
|
|
|
|
for ($k = 0; $k < count($a_url); $k++) {
|
|
array_shift($a_url[$k]);
|
|
array_shift($a_url[$k]);
|
|
}
|
|
array_shift($v_url);
|
|
array_shift($v_url);
|
|
|
|
$Start = 0;
|
|
$End = count($v_url);
|
|
|
|
if ($End > $URLListLimit) {
|
|
$Start = $End - $URLListLimit;
|
|
}
|
|
|
|
$TimelineFile = $WorkPath . "/" . $ChName . "/timeline.json";
|
|
|
|
$Timeline = [];
|
|
$ToDownload = 0;
|
|
if (file_exists($TimelineFile)) {
|
|
$Timeline = json_decode(file_get_contents($TimelineFile), true);
|
|
if (count($Timeline) > 50) {
|
|
array_shift($Timeline);
|
|
}
|
|
}
|
|
|
|
$aHeaderFile = $WorkPath . "/" . $ChName . "/cache/ainit_" . md5($aHeaderUrl) . ".mp4";
|
|
$vHeaderFile = $WorkPath . "/" . $ChName . "/cache/vinit_" . md5($vHeaderUrl) . ".mp4";
|
|
|
|
if ($aHeader == "") {
|
|
if (!file_exists($aHeaderFile)) {
|
|
DoLog("Downloading audio header: " . $aHeaderUrl);
|
|
$aHeader = DownloadRetry($aHeaderUrl, $UseProxy, $Proxy, $Useragent, $CustomHeaders);
|
|
if ($aHeader && !file_exists($aHeaderFile)) {
|
|
file_put_contents($aHeaderFile, $aHeader);
|
|
}
|
|
} else {
|
|
$aHeader = file_get_contents($aHeaderFile);
|
|
}
|
|
}
|
|
if ($vHeader == "") {
|
|
if (!file_exists($vHeaderFile)) {
|
|
DoLog("Downloading video header: " . $vHeaderUrl);
|
|
$vHeader = DownloadRetry($vHeaderUrl, $UseProxy, $Proxy, $Useragent, $CustomHeaders);
|
|
if ($vHeader && !file_exists($vHeaderFile)) {
|
|
file_put_contents($vHeaderFile, $vHeader);
|
|
}
|
|
} else {
|
|
$vHeader = file_get_contents($vHeaderFile);
|
|
}
|
|
}
|
|
|
|
DoLog("Determining timeline start/end");
|
|
for ($i = $Start; $i < $End; $i++) {
|
|
if ($v_url[$i] != "") {
|
|
$TimelineItem["v"] = $v_url[$i];
|
|
$TimelineItem["s"] = 0;
|
|
$TimelineItem["k"] = md5(microtime(true));
|
|
for ($k = 0; $k < count($a_url); $k++) {
|
|
$TimelineItem["a"][$k] = $a_url[$k][$i];
|
|
}
|
|
if (!in_Timeline($TimelineItem, $Timeline)) {
|
|
$Timeline[] = $TimelineItem;
|
|
}
|
|
}
|
|
}
|
|
|
|
DoLog("Calculating download queue");
|
|
$DTimeline = [];
|
|
for ($i = 0; $i < count($Timeline); $i++) {
|
|
if ($Timeline[$i]["s"] == 0) {
|
|
$DTimeline[] = $Timeline[$i];
|
|
if (count($DTimeline) == $SegmentJoiner) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (count($DTimeline) == $SegmentJoiner) {
|
|
DoLog("Process download queue");
|
|
$aData = [];
|
|
$vData = null;
|
|
/*
|
|
$tmp=DownloadList($DTimeline, $UseProxy, $Proxy);
|
|
for($k=0;$k<count($tmp["a"]);$k++){
|
|
$aData[$k].=$tmp["a"][$k];
|
|
}
|
|
$vData.=$tmp["v"];
|
|
$SegCounter+=count($DTimeline);
|
|
*/
|
|
|
|
for ($i = 0; $i < count($DTimeline); $i++) {
|
|
for ($k = 0; $k < count($DTimeline[$i]["a"]); $k++) {
|
|
DoLog(" Downloading audio segment: " . $DTimeline[$i]["a"][$k]);
|
|
$aData[$k] .= DownloadRetry($DTimeline[$i]["a"][$k], $UseProxy, $Proxy, $Useragent, $CustomHeaders);
|
|
}
|
|
DoLog(" Downloading video segment: " . $DTimeline[$i]["v"]);
|
|
$vData .= DownloadRetry($DTimeline[$i]["v"], $UseProxy, $Proxy, $Useragent, $CustomHeaders);
|
|
$SegmentCounter++;
|
|
DoLog(" Segments: $SegmentCounter / $SegmentJoiner done");
|
|
$SegCounter++;
|
|
}
|
|
|
|
DoLog("Finalizing segment");
|
|
JoinSegment($ChID, $ChName, $Keys, $aHeader, $aData, $vHeader, $vData, $DownloadIndex);
|
|
$aData = "";
|
|
$vData = "";
|
|
$SegmentCounter = 0;
|
|
$DownloadIndex++;
|
|
|
|
DoLog("Bookmark timeline");
|
|
for ($i = 0; $i < count($Timeline); $i++) {
|
|
for ($j = 0; $j < count($DTimeline); $j++) {
|
|
if ($Timeline[$i]["k"] == $DTimeline[$j]["k"]) {
|
|
$Timeline[$i]["s"] = 1;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
DoLog("Waiting for $SegmentJoiner segments in timeline queue");
|
|
}
|
|
for ($i = 0; $i < count($Timeline); $i++) {
|
|
if ($Timeline[$i]["s"] == 0) {
|
|
$ToDownload = $ToDownload + 1;
|
|
}
|
|
|
|
}
|
|
file_put_contents($TimelineFile, json_encode($Timeline));
|
|
$EndTime = time();
|
|
$WaitTime = ($UpdateInterval) - (($EndTime - $StartTime));
|
|
DoLog("Running round finished ..");
|
|
DoLog(" Start Time: " . date("Y-m-d H:i:s", $StartTime));
|
|
DoLog(" End Time: " . date("Y-m-d H:i:s", $EndTime));
|
|
DoLog(" Update Interval: " . $UpdateInterval . " seconds");
|
|
DoLog("Status:");
|
|
DoLog(" Timeline: " . $ToDownload . "/$SegmentJoiner");
|
|
DoLog(" Done: " . $SegCounter);
|
|
DoLog(" Waiting: " . $WaitTime . " seconds");
|
|
if ($WaitTime > 0) {
|
|
sleep($WaitTime);
|
|
}
|
|
goto Start;
|
|
}
|
|
}
|
|
} catch (Exception $e) {
|
|
DoLog($e->getMessage());
|
|
}
|