Guest User

KSV's AdobeHDS script

a guest
Feb 17th, 2013
33
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. <?php
  2.   define('AUDIO', 0x08);
  3.   define('VIDEO', 0x09);
  4.   define('SCRIPT_DATA', 0x12);
  5.   define('FRAME_TYPE_INFO', 0x05);
  6.   define('CODEC_ID_AVC', 0x07);
  7.   define('CODEC_ID_AAC', 0x0A);
  8.   define('AVC_SEQUENCE_HEADER', 0x00);
  9.   define('AAC_SEQUENCE_HEADER', 0x00);
  10.   define('AVC_SEQUENCE_END', 0x02);
  11.   define('FRAMEFIX_STEP', 40);
  12.   define('INVALID_TIMESTAMP', -1);
  13.  
  14.   class CLI
  15.     {
  16.       protected static $ACCEPTED = array(
  17.           0 => array(
  18.               'help'   => 'displays this help',
  19.               'debug'  => 'show debug output',
  20.               'delete' => 'delete fragments after processing',
  21.               'fproxy' => 'force proxy for downloading of fragments',
  22.               'play'   => 'dump stream to stdout for piping to media player',
  23.               'rename' => 'rename fragments sequentially before processing',
  24.               'update' => 'update the script to current git version'
  25.           ),
  26.           1 => array(
  27.               'auth'      => 'authentication string for fragment requests',
  28.               'duration'  => 'stop recording after specified number of seconds',
  29.               'filesize'  => 'split output file in chunks of specified size (MB)',
  30.               'fragments' => 'base filename for fragments',
  31.               'fixwindow' => 'timestamp gap between frames to consider as timeshift',
  32.               'manifest'  => 'manifest file for downloading of fragments',
  33.               'outdir'    => 'destination folder for output file',
  34.               'outfile'   => 'filename to use for output file',
  35.               'parallel'  => 'number of fragments to download simultaneously',
  36.               'proxy'     => 'proxy for downloading of manifest',
  37.               'quality'   => 'selected quality level (low|medium|high) or exact bitrate',
  38.               'referrer'  => 'Referer to use for emulation of browser requests',
  39.               'start'     => 'start from specified fragment',
  40.               'useragent' => 'User-Agent to use for emulation of browser requests'
  41.           )
  42.       );
  43.       var $params = array();
  44.  
  45.       function __construct()
  46.         {
  47.           global $argc, $argv;
  48.  
  49.           // Parse params
  50.           if ($argc > 1)
  51.             {
  52.               $paramSwitch = false;
  53.               for ($i = 1; $i < $argc; $i++)
  54.                 {
  55.                   $arg      = $argv[$i];
  56.                   $isSwitch = preg_match('/^--/', $arg);
  57.  
  58.                   if ($isSwitch)
  59.                       $arg = preg_replace('/^--/', '', $arg);
  60.  
  61.                   if ($paramSwitch && $isSwitch)
  62.                       LogError("[param] expected after '$paramSwitch' switch (" . self::$ACCEPTED[1][$paramSwitch] . ")");
  63.                   else if (!$paramSwitch && !$isSwitch)
  64.                     {
  65.                       if (isset($GLOBALS['baseFilename']) and (!$GLOBALS['baseFilename']))
  66.                           $GLOBALS['baseFilename'] = $arg;
  67.                       else
  68.                           LogError("'$arg' is an invalid switch, use --help to display valid switches.");
  69.                     }
  70.                   else if (!$paramSwitch && $isSwitch)
  71.                     {
  72.                       if (isset($this->params[$arg]))
  73.                           LogError("'$arg' switch cannot occur more than once");
  74.  
  75.                       $this->params[$arg] = true;
  76.                       if (isset(self::$ACCEPTED[1][$arg]))
  77.                           $paramSwitch = $arg;
  78.                       else if (!isset(self::$ACCEPTED[0][$arg]))
  79.                           LogError("there's no '$arg' switch, use --help to display all switches.");
  80.                     }
  81.                   else if ($paramSwitch && !$isSwitch)
  82.                     {
  83.                       $this->params[$paramSwitch] = $arg;
  84.                       $paramSwitch                = false;
  85.                     }
  86.                 }
  87.             }
  88.  
  89.           // Final check
  90.           foreach ($this->params as $k => $v)
  91.               if (isset(self::$ACCEPTED[1][$k]) && $v === true)
  92.                   LogError("[param] expected after '$k' switch (" . self::$ACCEPTED[1][$k] . ")");
  93.         }
  94.  
  95.       function getParam($name)
  96.         {
  97.           if (isset($this->params[$name]))
  98.               return $this->params[$name];
  99.           else
  100.               return "";
  101.         }
  102.  
  103.       function displayHelp()
  104.         {
  105.           LogInfo("You can use script with following switches: \n");
  106.           foreach (self::$ACCEPTED[0] as $key => $value)
  107.               LogInfo(sprintf(" --%-18s%s", $key, $value));
  108.           foreach (self::$ACCEPTED[1] as $key => $value)
  109.               LogInfo(sprintf(" --%-9s%-9s%s", $key, " [param]", $value));
  110.         }
  111.     }
  112.  
  113.   class cURL
  114.     {
  115.       var $headers, $user_agent, $compression, $cookie_file;
  116.       var $active, $cert_check, $fragProxy, $proxy, $response;
  117.       var $mh, $ch, $mrc;
  118.       static $ref = 0;
  119.  
  120.       function cURL($cookies = true, $cookie = 'Cookies.txt', $compression = 'gzip', $proxy = '')
  121.         {
  122.           $this->headers     = $this->headers();
  123.           $this->user_agent  = 'Mozilla/5.0 (Windows NT 5.1; rv:18.0) Gecko/20100101 Firefox/18.0';
  124.           $this->compression = $compression;
  125.           $this->cookies     = $cookies;
  126.           if ($this->cookies == true)
  127.               $this->cookie($cookie);
  128.           $this->cert_check = true;
  129.           $this->fragProxy  = false;
  130.           $this->proxy      = $proxy;
  131.           self::$ref++;
  132.         }
  133.  
  134.       function __destruct()
  135.         {
  136.           $this->stopDownloads();
  137.           if ((self::$ref <= 1) and file_exists($this->cookie_file))
  138.               unlink($this->cookie_file);
  139.           self::$ref--;
  140.         }
  141.  
  142.       function headers()
  143.         {
  144.           $headers[] = 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
  145.           $headers[] = 'Connection: Keep-Alive';
  146.           return $headers;
  147.         }
  148.  
  149.       function cookie($cookie_file)
  150.         {
  151.           if (file_exists($cookie_file))
  152.               $this->cookie_file = $cookie_file;
  153.           else
  154.             {
  155.               $file = fopen($cookie_file, 'w') or $this->error('The cookie file could not be opened. Make sure this directory has the correct permissions.');
  156.               $this->cookie_file = $cookie_file;
  157.               fclose($file);
  158.             }
  159.         }
  160.  
  161.       function get($url)
  162.         {
  163.           $process = curl_init($url);
  164.           curl_setopt($process, CURLOPT_HTTPHEADER, $this->headers);
  165.           curl_setopt($process, CURLOPT_HEADER, 0);
  166.           curl_setopt($process, CURLOPT_USERAGENT, $this->user_agent);
  167.           if ($this->cookies == true)
  168.             {
  169.               curl_setopt($process, CURLOPT_COOKIEFILE, $this->cookie_file);
  170.               curl_setopt($process, CURLOPT_COOKIEJAR, $this->cookie_file);
  171.             }
  172.           curl_setopt($process, CURLOPT_ENCODING, $this->compression);
  173.           curl_setopt($process, CURLOPT_TIMEOUT, 60);
  174.           if ($this->proxy)
  175.               $this->setProxy($process, $this->proxy);
  176.           curl_setopt($process, CURLOPT_RETURNTRANSFER, 1);
  177.           curl_setopt($process, CURLOPT_FOLLOWLOCATION, 1);
  178.           if (!$this->cert_check)
  179.               curl_setopt($process, CURLOPT_SSL_VERIFYPEER, 0);
  180.           $this->response = curl_exec($process);
  181.           if ($this->response !== false)
  182.               $status = curl_getinfo($process, CURLINFO_HTTP_CODE);
  183.           curl_close($process);
  184.           if (isset($status))
  185.               return $status;
  186.           else
  187.               return false;
  188.         }
  189.  
  190.       function post($url, $data)
  191.         {
  192.           $process   = curl_init($url);
  193.           $headers   = $this->headers;
  194.           $headers[] = 'Content-Type: application/x-www-form-urlencoded;charset=UTF-8';
  195.           curl_setopt($process, CURLOPT_HTTPHEADER, $headers);
  196.           curl_setopt($process, CURLOPT_HEADER, 1);
  197.           curl_setopt($process, CURLOPT_USERAGENT, $this->user_agent);
  198.           if ($this->cookies == true)
  199.             {
  200.               curl_setopt($process, CURLOPT_COOKIEFILE, $this->cookie_file);
  201.               curl_setopt($process, CURLOPT_COOKIEJAR, $this->cookie_file);
  202.             }
  203.           curl_setopt($process, CURLOPT_ENCODING, $this->compression);
  204.           curl_setopt($process, CURLOPT_TIMEOUT, 60);
  205.           if ($this->proxy)
  206.               $this->setProxy($process, $this->proxy);
  207.           curl_setopt($process, CURLOPT_POSTFIELDS, $data);
  208.           curl_setopt($process, CURLOPT_RETURNTRANSFER, 1);
  209.           curl_setopt($process, CURLOPT_FOLLOWLOCATION, 1);
  210.           curl_setopt($process, CURLOPT_POST, 1);
  211.           if (!$this->cert_check)
  212.               curl_setopt($process, CURLOPT_SSL_VERIFYPEER, 0);
  213.           $return = curl_exec($process);
  214.           curl_close($process);
  215.           return $return;
  216.         }
  217.  
  218.       function setProxy(&$process, $proxy)
  219.         {
  220.           $type = substr($proxy, 0, stripos($proxy, "://"));
  221.           if ($type)
  222.             {
  223.               $type  = strtolower($type);
  224.               $proxy = substr($proxy, stripos($proxy, "://") + 3);
  225.             }
  226.           switch ($type)
  227.           {
  228.               case "socks4":
  229.                   $type = CURLPROXY_SOCKS4;
  230.                   break;
  231.               case "socks5":
  232.                   $type = CURLPROXY_SOCKS5;
  233.                   break;
  234.               default:
  235.                   $type = CURLPROXY_HTTP;
  236.           }
  237.           curl_setopt($process, CURLOPT_PROXY, $proxy);
  238.           curl_setopt($process, CURLOPT_PROXYTYPE, $type);
  239.         }
  240.  
  241.       function addDownload($url, $id)
  242.         {
  243.           if (!isset($this->mh))
  244.               $this->mh = curl_multi_init();
  245.           if (isset($this->ch[$id]))
  246.               return;
  247.           else
  248.               $download =& $this->ch[$id];
  249.           $download['id']  = $id;
  250.           $download['url'] = $url;
  251.           $download['ch']  = curl_init($url);
  252.           curl_setopt($download['ch'], CURLOPT_HTTPHEADER, $this->headers);
  253.           curl_setopt($download['ch'], CURLOPT_HEADER, 0);
  254.           curl_setopt($download['ch'], CURLOPT_USERAGENT, $this->user_agent);
  255.           if ($this->cookies == true)
  256.             {
  257.               curl_setopt($download['ch'], CURLOPT_COOKIEFILE, $this->cookie_file);
  258.               curl_setopt($download['ch'], CURLOPT_COOKIEJAR, $this->cookie_file);
  259.             }
  260.           curl_setopt($download['ch'], CURLOPT_ENCODING, $this->compression);
  261.           curl_setopt($download['ch'], CURLOPT_TIMEOUT, 300);
  262.           if ($this->fragProxy and $this->proxy)
  263.               $this->setProxy($download['ch'], $this->proxy);
  264.           curl_setopt($download['ch'], CURLOPT_BINARYTRANSFER, 1);
  265.           curl_setopt($download['ch'], CURLOPT_RETURNTRANSFER, 1);
  266.           curl_setopt($download['ch'], CURLOPT_FOLLOWLOCATION, 1);
  267.           if (!$this->cert_check)
  268.               curl_setopt($download['ch'], CURLOPT_SSL_VERIFYPEER, 0);
  269.           curl_multi_add_handle($this->mh, $download['ch']);
  270.           do
  271.             {
  272.               $this->mrc = curl_multi_exec($this->mh, $this->active);
  273.             } while ($this->mrc == CURLM_CALL_MULTI_PERFORM);
  274.         }
  275.  
  276.       function checkDownloads()
  277.         {
  278.           if (isset($this->mh))
  279.             {
  280.               curl_multi_select($this->mh);
  281.               $this->mrc = curl_multi_exec($this->mh, $this->active);
  282.               if ($this->mrc != CURLM_OK)
  283.                   return false;
  284.               while ($info = curl_multi_info_read($this->mh))
  285.                 {
  286.                   foreach ($this->ch as $download)
  287.                       if ($download['ch'] == $info['handle'])
  288.                           break;
  289.                   $info         = curl_getinfo($download['ch']);
  290.                   $array['id']  = $download['id'];
  291.                   $array['url'] = $download['url'];
  292.                   if ($info['http_code'] == 200)
  293.                     {
  294.                       if ($info['size_download'] >= $info['download_content_length'])
  295.                         {
  296.                           $array['status']   = $info['http_code'];
  297.                           $array['response'] = curl_multi_getcontent($download['ch']);
  298.                         }
  299.                       else
  300.                         {
  301.                           $array['status']   = false;
  302.                           $array['response'] = "";
  303.                         }
  304.                     }
  305.                   else
  306.                     {
  307.                       $array['status']   = $info['http_code'];
  308.                       $array['response'] = curl_multi_getcontent($download['ch']);
  309.                     }
  310.                   $downloads[] = $array;
  311.                   curl_multi_remove_handle($this->mh, $download['ch']);
  312.                   curl_close($download['ch']);
  313.                   unset($this->ch[$download['id']]);
  314.                 }
  315.               if (isset($downloads) and (count($downloads) > 0))
  316.                   return $downloads;
  317.             }
  318.           return false;
  319.         }
  320.  
  321.       function stopDownloads()
  322.         {
  323.           if (isset($this->mh))
  324.             {
  325.               if (isset($this->ch))
  326.                 {
  327.                   foreach ($this->ch as $download)
  328.                     {
  329.                       curl_multi_remove_handle($this->mh, $download['ch']);
  330.                       curl_close($download['ch']);
  331.                     }
  332.                   unset($this->ch);
  333.                 }
  334.               curl_multi_close($this->mh);
  335.               unset($this->mh);
  336.             }
  337.         }
  338.  
  339.       function error($error)
  340.         {
  341.           LogError("cURL Error : $error");
  342.         }
  343.     }
  344.  
  345.   class F4F
  346.     {
  347.       var $audio, $auth, $baseFilename, $baseTS, $bootstrapUrl, $baseUrl, $debug, $duration, $fileCount, $filesize;
  348.       var $fixWindow, $format, $live, $media, $outDir, $outFile, $parallel, $play, $processed, $quality, $rename, $video;
  349.       var $prevTagSize, $tagHeaderLen;
  350.       var $segTable, $fragTable, $segNum, $fragNum, $frags, $fragCount, $fragsPerSeg, $lastFrag, $fragUrl, $discontinuity;
  351.       var $prevAudioTS, $prevVideoTS, $pAudioTagLen, $pVideoTagLen, $pAudioTagPos, $pVideoTagPos;
  352.       var $prevAVC_Header, $prevAAC_Header, $AVC_HeaderWritten, $AAC_HeaderWritten;
  353.  
  354.       function __construct()
  355.         {
  356.           $this->auth          = "";
  357.           $this->baseFilename  = "";
  358.           $this->bootstrapUrl  = "";
  359.           $this->debug         = false;
  360.           $this->duration      = 0;
  361.           $this->fileCount     = 1;
  362.           $this->fixWindow     = 1000;
  363.           $this->format        = "";
  364.           $this->live          = false;
  365.           $this->outDir        = "";
  366.           $this->outFile       = "";
  367.           $this->parallel      = 8;
  368.           $this->play          = false;
  369.           $this->processed     = false;
  370.           $this->quality       = "high";
  371.           $this->rename        = false;
  372.           $this->segTable      = array();
  373.           $this->fragTable     = array();
  374.           $this->segStart      = false;
  375.           $this->fragStart     = false;
  376.           $this->frags         = array();
  377.           $this->fragCount     = 0;
  378.           $this->fragsPerSeg   = 0;
  379.           $this->lastFrag      = 0;
  380.           $this->discontinuity = "";
  381.           $this->InitDecoder();
  382.         }
  383.  
  384.       function InitDecoder()
  385.         {
  386.           $this->audio             = false;
  387.           $this->baseTS            = INVALID_TIMESTAMP;
  388.           $this->filesize          = 0;
  389.           $this->video             = false;
  390.           $this->prevTagSize       = 4;
  391.           $this->tagHeaderLen      = 11;
  392.           $this->prevAudioTS       = INVALID_TIMESTAMP;
  393.           $this->prevVideoTS       = INVALID_TIMESTAMP;
  394.           $this->pAudioTagLen      = 0;
  395.           $this->pVideoTagLen      = 0;
  396.           $this->pAudioTagPos      = 0;
  397.           $this->pVideoTagPos      = 0;
  398.           $this->prevAVC_Header    = false;
  399.           $this->prevAAC_Header    = false;
  400.           $this->AVC_HeaderWritten = false;
  401.           $this->AAC_HeaderWritten = false;
  402.         }
  403.  
  404.       function GetManifest($cc, $manifest)
  405.         {
  406.           $status = $cc->get($manifest);
  407.           if ($status == 403)
  408.               LogError("Access Denied! Unable to download the manifest.");
  409.           else if ($status != 200)
  410.               LogError("Unable to download the manifest");
  411.           $xml = simplexml_load_string(trim($cc->response));
  412.           if (!$xml)
  413.               LogError("Failed to load xml");
  414.           $namespace = $xml->getDocNamespaces();
  415.           $namespace = $namespace[''];
  416.           $xml->registerXPathNamespace("ns", $namespace);
  417.           return $xml;
  418.         }
  419.  
  420.       function ParseManifest($cc, $manifest)
  421.         {
  422.           LogInfo("Processing manifest info....");
  423.           $xml     = $this->GetManifest($cc, $manifest);
  424.           $baseUrl = $xml->xpath("/ns:manifest/ns:baseURL");
  425.           if (isset($baseUrl[0]))
  426.               $baseUrl = GetString($baseUrl[0]);
  427.           else
  428.               $baseUrl = "";
  429.           $url = $xml->xpath("/ns:manifest/ns:media[@*]");
  430.           if (isset($url[0]['href']))
  431.             {
  432.               foreach ($url as $manifest)
  433.                 {
  434.                   $bitrate = (int) $manifest['bitrate'];
  435.                   $entry =& $manifests[$bitrate];
  436.                   $entry['bitrate'] = $bitrate;
  437.                   $href             = GetString($manifest['href']);
  438.                   $entry['url']     = NormalizePath(JoinUrl($baseUrl, $href));
  439.                   $entry['xml']     = $this->GetManifest($cc, $entry['url']);
  440.                 }
  441.             }
  442.           else
  443.             {
  444.               $manifests[0]['bitrate'] = 0;
  445.               $manifests[0]['url']     = $manifest;
  446.               $manifests[0]['xml']     = $xml;
  447.             }
  448.  
  449.           foreach ($manifests as $manifest)
  450.             {
  451.               $xml = $manifest['xml'];
  452.  
  453.               // Extract baseUrl from manifest url
  454.               $baseUrl = $xml->xpath("/ns:manifest/ns:baseURL");
  455.               if (isset($baseUrl[0]))
  456.                   $baseUrl = GetString($baseUrl[0]);
  457.               else
  458.                 {
  459.                   $baseUrl = $manifest['url'];
  460.                   if (strpos($baseUrl, '?') !== false)
  461.                       $baseUrl = substr($baseUrl, 0, strpos($baseUrl, '?'));
  462.                   $baseUrl = substr($baseUrl, 0, strrpos($baseUrl, '/'));
  463.                 }
  464.               if (!isHttpUrl($baseUrl))
  465.                   LogError("Provided manifest is not a valid HDS manifest");
  466.  
  467.               $streams = $xml->xpath("/ns:manifest/ns:media");
  468.               foreach ($streams as $stream)
  469.                 {
  470.                   $array = array();
  471.                   foreach ($stream->attributes() as $k => $v)
  472.                       $array[strtolower($k)] = GetString($v);
  473.                   $array['metadata'] = GetString($stream->{'metadata'});
  474.                   $stream            = $array;
  475.  
  476.                   $bitrate  = isset($stream['bitrate']) ? (int) $stream['bitrate'] : $manifest['bitrate'];
  477.                   $streamId = isset($stream[strtolower('streamId')]) ? $stream[strtolower('streamId')] : "";
  478.                   $mediaEntry =& $this->media[$bitrate];
  479.  
  480.                   $mediaEntry['baseUrl'] = $baseUrl;
  481.                   if (substr($stream['url'], 0, 1) == "/")
  482.                       $mediaEntry['url'] = substr($stream['url'], 1);
  483.                   else
  484.                       $mediaEntry['url'] = $stream['url'];
  485.                   if (isset($stream[strtolower('bootstrapInfoId')]))
  486.                       $bootstrap = $xml->xpath("/ns:manifest/ns:bootstrapInfo[@id='" . $stream[strtolower('bootstrapInfoId')] . "']");
  487.                   else
  488.                       $bootstrap = $xml->xpath("/ns:manifest/ns:bootstrapInfo");
  489.                   if (isset($bootstrap[0]['url']))
  490.                     {
  491.                       $bootstrapUrl = GetString($bootstrap[0]['url']);
  492.                       if (!isHttpUrl($bootstrapUrl))
  493.                           $bootstrapUrl = JoinUrl($mediaEntry['baseUrl'], $bootstrapUrl);
  494.                       $mediaEntry['bootstrapUrl'] = NormalizePath($bootstrapUrl);
  495.                       if ($cc->get($mediaEntry['bootstrapUrl']) != 200)
  496.                           LogError("Failed to get bootstrap info");
  497.                       $mediaEntry['bootstrap'] = $cc->response;
  498.                     }
  499.                   else
  500.                       $mediaEntry['bootstrap'] = base64_decode(GetString($bootstrap[0]));
  501.                   if (isset($stream['metadata']))
  502.                       $mediaEntry['metadata'] = base64_decode($stream['metadata']);
  503.                   else
  504.                       $mediaEntry['metadata'] = "";
  505.                 }
  506.               unset($mediaEntry);
  507.             }
  508.  
  509.           // Available qualities
  510.           $bitrates = array();
  511.           if (!count($this->media))
  512.               LogError("No media entry found");
  513.           krsort($this->media, SORT_NUMERIC);
  514.           LogDebug("Manifest Entries:\n");
  515.           LogDebug(sprintf(" %-8s%s", "Bitrate", "URL"));
  516.           for ($i = 0; $i < count($this->media); $i++)
  517.             {
  518.               $key        = KeyName($this->media, $i);
  519.               $bitrates[] = $key;
  520.               LogDebug(sprintf(" %-8d%s", $key, $this->media[$key]['url']));
  521.             }
  522.           LogDebug("");
  523.           LogInfo("Quality Selection:\n Available: " . implode(' ', $bitrates));
  524.  
  525.           // Quality selection
  526.           if (is_numeric($this->quality) and isset($this->media[$this->quality]))
  527.             {
  528.               $key         = $this->quality;
  529.               $this->media = $this->media[$key];
  530.             }
  531.           else
  532.             {
  533.               $this->quality = strtolower($this->quality);
  534.               switch ($this->quality)
  535.               {
  536.                   case "low":
  537.                       $this->quality = 2;
  538.                       break;
  539.                   case "medium":
  540.                       $this->quality = 1;
  541.                       break;
  542.                   default:
  543.                       $this->quality = 0;
  544.               }
  545.               while ($this->quality >= 0)
  546.                 {
  547.                   $key = KeyName($this->media, $this->quality);
  548.                   if ($key !== NULL)
  549.                     {
  550.                       $this->media = $this->media[$key];
  551.                       break;
  552.                     }
  553.                   else
  554.                       $this->quality -= 1;
  555.                 }
  556.             }
  557.           LogInfo(" Selected : " . $key);
  558.  
  559.           $this->baseUrl = $this->media['baseUrl'];
  560.           if (isset($this->media['bootstrapUrl']))
  561.               $this->bootstrapUrl = $this->media['bootstrapUrl'];
  562.           $bootstrapInfo = $this->media['bootstrap'];
  563.           ReadBoxHeader($bootstrapInfo, $pos, $boxType, $boxSize);
  564.           if ($boxType == "abst")
  565.               $this->ParseBootstrapBox($bootstrapInfo, $pos);
  566.           else
  567.               LogError("Failed to parse bootstrap info");
  568.         }
  569.  
  570.       function UpdateBootstrapInfo($cc, $bootstrapUrl)
  571.         {
  572.           $fragNum = $this->fragCount;
  573.           $retries = 0;
  574.           while (($fragNum == $this->fragCount) and ($retries < 30))
  575.             {
  576.               $bootstrapPos = 0;
  577.               LogDebug("Updating bootstrap info, Available fragments: " . $this->fragCount);
  578.               if ($cc->get($bootstrapUrl) != 200)
  579.                   LogError("Failed to refresh bootstrap info");
  580.               $bootstrapInfo = $cc->response;
  581.               ReadBoxHeader($bootstrapInfo, $bootstrapPos, $boxType, $boxSize);
  582.               if ($boxType == "abst")
  583.                   $this->ParseBootstrapBox($bootstrapInfo, $bootstrapPos);
  584.               else
  585.                   LogError("Failed to parse bootstrap info");
  586.               LogDebug("Update complete, Available fragments: " . $this->fragCount);
  587.               if ($fragNum == $this->fragCount)
  588.                 {
  589.                   LogInfo("Updating bootstrap info, Retries: " . ++$retries, true);
  590.                   usleep(4000000);
  591.                 }
  592.             }
  593.         }
  594.  
  595.       function ParseBootstrapBox($bootstrapInfo, $pos)
  596.         {
  597.           $version          = ReadByte($bootstrapInfo, $pos);
  598.           $flags            = ReadInt24($bootstrapInfo, $pos + 1);
  599.           $bootstrapVersion = ReadInt32($bootstrapInfo, $pos + 4);
  600.           $byte             = ReadByte($bootstrapInfo, $pos + 8);
  601.           $profile          = ($byte & 0xC0) >> 6;
  602.           if (($byte & 0x20) >> 5)
  603.               $this->live = true;
  604.           $update              = ($byte & 0x10) >> 4;
  605.           $timescale           = ReadInt32($bootstrapInfo, $pos + 9);
  606.           $currentMediaTime    = ReadInt64($bootstrapInfo, 13);
  607.           $smpteTimeCodeOffset = ReadInt64($bootstrapInfo, 21);
  608.           $pos += 29;
  609.           $movieIdentifier  = ReadString($bootstrapInfo, $pos);
  610.           $serverEntryCount = ReadByte($bootstrapInfo, $pos++);
  611.           for ($i = 0; $i < $serverEntryCount; $i++)
  612.               $serverEntryTable[$i] = ReadString($bootstrapInfo, $pos);
  613.           $qualityEntryCount = ReadByte($bootstrapInfo, $pos++);
  614.           for ($i = 0; $i < $qualityEntryCount; $i++)
  615.               $qualityEntryTable[$i] = ReadString($bootstrapInfo, $pos);
  616.           $drmData          = ReadString($bootstrapInfo, $pos);
  617.           $metadata         = ReadString($bootstrapInfo, $pos);
  618.           $segRunTableCount = ReadByte($bootstrapInfo, $pos++);
  619.           for ($i = 0; $i < $segRunTableCount; $i++)
  620.             {
  621.               ReadBoxHeader($bootstrapInfo, $pos, $boxType, $boxSize);
  622.               if ($boxType == "asrt")
  623.                   $this->ParseAsrtBox($bootstrapInfo, $pos);
  624.               $pos += $boxSize;
  625.             }
  626.           $fragRunTableCount = ReadByte($bootstrapInfo, $pos++);
  627.           for ($i = 0; $i < $fragRunTableCount; $i++)
  628.             {
  629.               ReadBoxHeader($bootstrapInfo, $pos, $boxType, $boxSize);
  630.               if ($boxType == "afrt")
  631.                   $this->ParseAfrtBox($bootstrapInfo, $pos);
  632.               $pos += $boxSize;
  633.             }
  634.         }
  635.  
  636.       function ParseAsrtBox($asrt, $pos)
  637.         {
  638.           $version           = ReadByte($asrt, $pos);
  639.           $flags             = ReadInt24($asrt, $pos + 1);
  640.           $qualityEntryCount = ReadByte($asrt, $pos + 4);
  641.           $pos += 5;
  642.           for ($i = 0; $i < $qualityEntryCount; $i++)
  643.               $qualitySegmentUrlModifiers[$i] = ReadString($asrt, $pos);
  644.           $segCount = ReadInt32($asrt, $pos);
  645.           $pos += 4;
  646.           LogDebug(sprintf("%s:\n\n %-8s%-10s", "Segment Entries", "Number", "Fragments"));
  647.           for ($i = 0; $i < $segCount; $i++)
  648.             {
  649.               $firstSegment = ReadInt32($asrt, $pos);
  650.               $segEntry =& $this->segTable[$firstSegment];
  651.               $segEntry['firstSegment']        = $firstSegment;
  652.               $segEntry['fragmentsPerSegment'] = ReadInt32($asrt, $pos + 4);
  653.               if ($segEntry['fragmentsPerSegment'] & 0x80000000)
  654.                   $segEntry['fragmentsPerSegment'] = 0;
  655.               $pos += 8;
  656.             }
  657.           unset($segEntry);
  658.           foreach ($this->segTable as $segEntry)
  659.               LogDebug(sprintf(" %-8s%-10s", $segEntry['firstSegment'], $segEntry['fragmentsPerSegment']));
  660.           LogDebug("");
  661.           $lastSegment = end($this->segTable);
  662.           if ($this->segStart === false)
  663.               $this->segStart = $lastSegment['firstSegment'];
  664.           $this->fragCount = $lastSegment['fragmentsPerSegment'];
  665.  
  666.           // Use segment table in case of multiple segments
  667.           if (count($this->segTable) > 1)
  668.             {
  669.               $secondLastSegment = prev($this->segTable);
  670.               if ($this->fragStart === false)
  671.                 {
  672.                   $this->fragsPerSeg = $secondLastSegment['fragmentsPerSegment'];
  673.                   $this->fragStart   = $secondLastSegment['firstSegment'] * $this->fragsPerSeg + $this->fragCount - 2;
  674.                   $this->fragCount   = $secondLastSegment['firstSegment'] * $this->fragsPerSeg + $this->fragCount;
  675.                 }
  676.               else
  677.                   $this->fragCount = $secondLastSegment['firstSegment'] * $this->fragsPerSeg + $this->fragCount;
  678.             }
  679.         }
  680.  
  681.       function ParseAfrtBox($afrt, $pos)
  682.         {
  683.           $version           = ReadByte($afrt, $pos);
  684.           $flags             = ReadInt24($afrt, $pos + 1);
  685.           $timescale         = ReadInt32($afrt, $pos + 4);
  686.           $qualityEntryCount = ReadByte($afrt, $pos + 8);
  687.           $pos += 9;
  688.           for ($i = 0; $i < $qualityEntryCount; $i++)
  689.               $qualitySegmentUrlModifiers[$i] = ReadString($afrt, $pos);
  690.           $fragEntries = ReadInt32($afrt, $pos);
  691.           $pos += 4;
  692.           LogDebug(sprintf("%s:\n\n %-8s%-16s%-16s%-16s", "Fragment Entries", "Number", "Timestamp", "Duration", "Discontinuity"));
  693.           for ($i = 0; $i < $fragEntries; $i++)
  694.             {
  695.               $firstFragment = ReadInt32($afrt, $pos);
  696.               $fragEntry =& $this->fragTable[$firstFragment];
  697.               $fragEntry['firstFragment']          = $firstFragment;
  698.               $fragEntry['firstFragmentTimestamp'] = ReadInt64($afrt, $pos + 4);
  699.               $fragEntry['fragmentDuration']       = ReadInt32($afrt, $pos + 12);
  700.               $fragEntry['discontinuityIndicator'] = "";
  701.               $pos += 16;
  702.               if ($fragEntry['fragmentDuration'] == 0)
  703.                   $fragEntry['discontinuityIndicator'] = ReadByte($afrt, $pos++);
  704.             }
  705.           unset($fragEntry);
  706.           foreach ($this->fragTable as $fragEntry)
  707.               LogDebug(sprintf(" %-8s%-16s%-16s%-16s", $fragEntry['firstFragment'], $fragEntry['firstFragmentTimestamp'], $fragEntry['fragmentDuration'], $fragEntry['discontinuityIndicator']));
  708.           LogDebug("");
  709.  
  710.           // Use fragment table in case of single segment
  711.           if (count($this->segTable) == 1)
  712.             {
  713.               $firstFragment = reset($this->fragTable);
  714.               $lastFragment  = end($this->fragTable);
  715.               if ($this->fragStart === false)
  716.                 {
  717.                   if ($this->live)
  718.                       $this->fragStart = $lastFragment['firstFragment'] - 2;
  719.                   else
  720.                       $this->fragStart = $firstFragment['firstFragment'] - 1;
  721.                   if ($this->fragStart < 0)
  722.                       $this->fragStart = 0;
  723.                 }
  724.               if ($this->fragCount > 0)
  725.                   $this->fragCount += $firstFragment['firstFragment'] - 1;
  726.               if ($this->fragCount < $lastFragment['firstFragment'])
  727.                   $this->fragCount = $lastFragment['firstFragment'];
  728.             }
  729.         }
  730.  
  731.       function DownloadFragments($cc, $manifest, $opt = array())
  732.         {
  733.           $start = 0;
  734.           extract($opt, EXTR_IF_EXISTS);
  735.  
  736.           $this->ParseManifest($cc, $manifest);
  737.           $segNum  = $this->segStart;
  738.           $fragNum = $this->fragStart;
  739.           if ($start)
  740.             {
  741.               if ($segNum > 1)
  742.                   if ($start % $this->fragsPerSeg)
  743.                       $segNum = (int) ($start / $this->fragsPerSeg + 1);
  744.                   else
  745.                       $segNum = (int) ($start / $this->fragsPerSeg);
  746.               $fragNum         = $start - 1;
  747.               $this->segStart  = $segNum;
  748.               $this->fragStart = $fragNum;
  749.             }
  750.           $this->lastFrag  = $fragNum;
  751.           $opt['cc']       = $cc;
  752.           $opt['duration'] = 0;
  753.  
  754.           // Extract baseFilename
  755.           $this->baseFilename = $this->media['url'];
  756.           if (substr($this->baseFilename, -1) == '/')
  757.               $this->baseFilename = substr($this->baseFilename, 0, -1);
  758.           $this->baseFilename = RemoveExtension($this->baseFilename);
  759.           if (strrpos($this->baseFilename, '/'))
  760.               $this->baseFilename = substr($this->baseFilename, strrpos($this->baseFilename, '/') + 1);
  761.           if (strpos($manifest, "?"))
  762.               $this->baseFilename = md5(substr($manifest, 0, strpos($manifest, "?"))) . "_" . $this->baseFilename;
  763.           else
  764.               $this->baseFilename = md5($manifest) . "_" . $this->baseFilename;
  765.           $this->baseFilename .= "Seg" . $segNum . "-Frag";
  766.  
  767.           if ($fragNum >= $this->fragCount)
  768.               LogError("No fragment available for downloading");
  769.  
  770.           if (isHttpUrl($this->media['url']))
  771.               $this->fragUrl = $this->media['url'];
  772.           else
  773.               $this->fragUrl = JoinUrl($this->baseUrl, $this->media['url']);
  774.           $this->fragUrl = NormalizePath($this->fragUrl);
  775.           LogDebug("Base Fragment Url:\n" . $this->fragUrl . "\n");
  776.           LogDebug("Downloading Fragments:\n");
  777.  
  778.           while (($fragNum < $this->fragCount) or $cc->active)
  779.             {
  780.               while ((count($cc->ch) < $this->parallel) and ($fragNum < $this->fragCount))
  781.                 {
  782.                   $frag       = array();
  783.                   $fragNum    = $fragNum + 1;
  784.                   $frag['id'] = $fragNum;
  785.                   LogInfo("Downloading $fragNum/$this->fragCount fragments", true);
  786.                   if (in_array_field($fragNum, "firstFragment", $this->fragTable, true))
  787.                       $this->discontinuity = value_in_array_field($fragNum, "firstFragment", "discontinuityIndicator", $this->fragTable, true);
  788.                   else
  789.                     {
  790.                       $closest = 1;
  791.                       foreach ($this->fragTable as $item)
  792.                         {
  793.                           if ($item['firstFragment'] < $fragNum)
  794.                               $closest = $item['firstFragment'];
  795.                           else
  796.                               break;
  797.                         }
  798.                       $this->discontinuity = value_in_array_field($closest, "firstFragment", "discontinuityIndicator", $this->fragTable, true);
  799.                     }
  800.                   if (($this->discontinuity == 1) or ($this->discontinuity == 3))
  801.                     {
  802.                       LogDebug("Skipping fragment $fragNum due to discontinuity");
  803.                       $frag['response'] = false;
  804.                       $this->rename     = true;
  805.                     }
  806.                   else if (file_exists($this->baseFilename . $fragNum))
  807.                     {
  808.                       LogDebug("Fragment $fragNum is already downloaded");
  809.                       $frag['response'] = file_get_contents($this->baseFilename . $fragNum);
  810.                     }
  811.                   if (isset($frag['response']))
  812.                     {
  813.                       if ($this->WriteFragment($frag, $opt) === 2)
  814.                           break 2;
  815.                       else
  816.                           continue;
  817.                     }
  818.  
  819.                   /* Increase or decrease segment number if current fragment is not available */
  820.                   /* in selected segment range                                                */
  821.                   if (count($this->segTable) > 1)
  822.                     {
  823.                       if ($fragNum > ($segNum * $this->fragsPerSeg))
  824.                           $segNum++;
  825.                       else if ($fragNum <= (($segNum - 1) * $this->fragsPerSeg))
  826.                           $segNum--;
  827.                     }
  828.  
  829.                   LogDebug("Adding fragment $fragNum to download queue");
  830.                   $cc->addDownload($this->fragUrl . "Seg" . $segNum . "-Frag" . $fragNum . $this->auth, $fragNum);
  831.                 }
  832.  
  833.               $downloads = $cc->checkDownloads();
  834.               if ($downloads !== false)
  835.                 {
  836.                   for ($i = 0; $i < count($downloads); $i++)
  837.                     {
  838.                       $frag       = array();
  839.                       $download   = $downloads[$i];
  840.                       $frag['id'] = $download['id'];
  841.                       if ($download['status'] == 200)
  842.                         {
  843.                           if ($this->VerifyFragment($download['response']))
  844.                             {
  845.                               LogDebug("Fragment " . $this->baseFilename . $download['id'] . " successfully downloaded");
  846.                               if (!($this->live or $this->play))
  847.                                   file_put_contents($this->baseFilename . $download['id'], $download['response']);
  848.                               $frag['response'] = $download['response'];
  849.                             }
  850.                           else
  851.                             {
  852.                               LogDebug("Fragment " . $download['id'] . " failed to verify");
  853.                               LogDebug("Adding fragment " . $download['id'] . " to download queue");
  854.                               $cc->addDownload($download['url'], $download['id']);
  855.                             }
  856.                         }
  857.                       else if ($download['status'] === false)
  858.                         {
  859.                           LogDebug("Fragment " . $download['id'] . " failed to download");
  860.                           LogDebug("Adding fragment " . $download['id'] . " to download queue");
  861.                           $cc->addDownload($download['url'], $download['id']);
  862.                         }
  863.                       else if ($download['status'] == 403)
  864.                           LogError("Access Denied! Unable to download fragments.");
  865.                       else
  866.                         {
  867.                           LogDebug("Fragment " . $download['id'] . " doesn't exist, Status: " . $download['status']);
  868.                           $frag['response'] = false;
  869.                           $this->rename     = true;
  870.  
  871.                           /* Resync with latest available fragment when we are left behind due to */
  872.                           /* slow connection and short live window on streaming server. make sure */
  873.                           /* to reset the last written fragment.                                  */
  874.                           if ($this->live and ($i + 1 == count($downloads)) and !$cc->active)
  875.                             {
  876.                               LogDebug("Trying to resync with latest available fragment");
  877.                               if ($this->WriteFragment($frag, $opt) === 2)
  878.                                   break 2;
  879.                               unset($frag['response']);
  880.                               $this->UpdateBootstrapInfo($cc, $this->bootstrapUrl);
  881.                               $fragNum        = $this->fragCount - 1;
  882.                               $this->lastFrag = $fragNum;
  883.                             }
  884.                         }
  885.                       if (isset($frag['response']))
  886.                           if ($this->WriteFragment($frag, $opt) === 2)
  887.                               break 2;
  888.                     }
  889.                   unset($downloads, $download);
  890.                 }
  891.               if ($this->live and ($fragNum >= $this->fragCount) and !$cc->active)
  892.                   $this->UpdateBootstrapInfo($cc, $this->bootstrapUrl);
  893.             }
  894.  
  895.           LogInfo("");
  896.           LogDebug("\nAll fragments downloaded successfully\n");
  897.           $cc->stopDownloads();
  898.           $this->processed = true;
  899.         }
  900.  
  901.       function VerifyFragment(&$frag)
  902.         {
  903.           $fragPos = 0;
  904.           $fragLen = strlen($frag);
  905.  
  906.           /* Some moronic servers add wrong boxSize in header causing fragment verification *
  907.            * to fail so we have to fix the boxSize before processing the fragment.          */
  908.           while ($fragPos < $fragLen)
  909.             {
  910.               ReadBoxHeader($frag, $fragPos, $boxType, $boxSize);
  911.               if ($boxType == "mdat")
  912.                 {
  913.                   $len = strlen(substr($frag, $fragPos, $boxSize));
  914.                   if ($boxSize and ($len == $boxSize))
  915.                       return true;
  916.                   else
  917.                     {
  918.                       $boxSize = $fragLen - $fragPos;
  919.                       WriteBoxSize($frag, $fragPos, $boxType, $boxSize);
  920.                       return true;
  921.                     }
  922.                 }
  923.               $fragPos += $boxSize;
  924.             }
  925.           return false;
  926.         }
  927.  
  928.       function RenameFragments($baseFilename, $fragNum, $fileExt)
  929.         {
  930.           $files   = array();
  931.           $retries = 0;
  932.  
  933.           while (true)
  934.             {
  935.               if ($retries >= 50)
  936.                   break;
  937.               $file = $baseFilename . ++$fragNum;
  938.               if (file_exists($file))
  939.                 {
  940.                   $files[] = $file;
  941.                   $retries = 0;
  942.                 }
  943.               else if (file_exists($file . $fileExt))
  944.                 {
  945.                   $files[] = $file;
  946.                   $retries = 0;
  947.                 }
  948.               else
  949.                   $retries++;
  950.             }
  951.  
  952.           $fragCount = count($files);
  953.           natsort($files);
  954.           for ($i = 0; $i < $fragCount; $i++)
  955.               rename($files[$i], $baseFilename . ($i + 1));
  956.         }
  957.  
  958.       function WriteMetadata($flv = false)
  959.         {
  960.           if (isset($this->media) and $this->media['metadata'])
  961.             {
  962.               $metadataSize = strlen($this->media['metadata']);
  963.               WriteByte($metadata, 0, SCRIPT_DATA);
  964.               WriteInt24($metadata, 1, $metadataSize);
  965.               WriteInt24($metadata, 4, 0);
  966.               WriteInt32($metadata, 7, 0);
  967.               $metadata = implode("", $metadata) . $this->media['metadata'];
  968.               WriteByte($metadata, $this->tagHeaderLen + $metadataSize - 1, 0x09);
  969.               WriteInt32($metadata, $this->tagHeaderLen + $metadataSize, $this->tagHeaderLen + $metadataSize);
  970.               if (is_resource($flv))
  971.                 {
  972.                   fwrite($flv, $metadata, $this->tagHeaderLen + $metadataSize + $this->prevTagSize);
  973.                   return true;
  974.                 }
  975.               else
  976.                   return $metadata;
  977.             }
  978.           return false;
  979.         }
  980.  
  981.       function WriteFlvTimestamp(&$frag, $fragPos, $packetTS)
  982.         {
  983.           WriteInt24($frag, $fragPos + 4, ($packetTS & 0x00FFFFFF));
  984.           WriteByte($frag, $fragPos + 7, ($packetTS & 0xFF000000) >> 24);
  985.         }
  986.  
  987.       function DecodeFragment($frag, $fragNum, $opt = array())
  988.         {
  989.           $debug = $this->debug;
  990.           $flv   = false;
  991.           extract($opt, EXTR_IF_EXISTS);
  992.  
  993.           $flvData  = "";
  994.           $fragPos  = 0;
  995.           $packetTS = 0;
  996.           $fragLen  = strlen($frag);
  997.  
  998.           if (!$this->VerifyFragment($frag))
  999.             {
  1000.               LogInfo("Skipping fragment number $fragNum");
  1001.               return false;
  1002.             }
  1003.  
  1004.           while ($fragPos < $fragLen)
  1005.             {
  1006.               ReadBoxHeader($frag, $fragPos, $boxType, $boxSize);
  1007.               if ($boxType == "mdat")
  1008.                 {
  1009.                   $fragLen = $fragPos + $boxSize;
  1010.                   break;
  1011.                 }
  1012.               $fragPos += $boxSize;
  1013.             }
  1014.  
  1015.           LogDebug(sprintf("\nFragment %d:\n" . $this->format . "%-16s", $fragNum, "Type", "CurrentTS", "PreviousTS", "Size", "Position"), $debug);
  1016.           while ($fragPos < $fragLen)
  1017.             {
  1018.               $packetType = ReadByte($frag, $fragPos);
  1019.               $packetSize = ReadInt24($frag, $fragPos + 1);
  1020.               $packetTS   = ReadInt24($frag, $fragPos + 4);
  1021.               $packetTS   = $packetTS | (ReadByte($frag, $fragPos + 7) << 24);
  1022.               if ($packetTS & 0x80000000)
  1023.                   $packetTS &= 0x7FFFFFFF;
  1024.               $totalTagLen = $this->tagHeaderLen + $packetSize + $this->prevTagSize;
  1025.  
  1026.               // Try to fix the odd timestamps and make them zero based
  1027.               $currentTS = $packetTS;
  1028.               $lastTS    = $this->prevVideoTS >= $this->prevAudioTS ? $this->prevVideoTS : $this->prevAudioTS;
  1029.               if (($this->baseTS == INVALID_TIMESTAMP) and (($packetType == AUDIO) or ($packetType == VIDEO)))
  1030.                   $this->baseTS = $packetTS;
  1031.               if ($this->baseTS > 1000)
  1032.                 {
  1033.                   if ($packetTS >= $this->baseTS)
  1034.                       $packetTS -= $this->baseTS;
  1035.                   else
  1036.                       $packetTS = $lastTS + FRAMEFIX_STEP;
  1037.                 }
  1038.               if ($lastTS != INVALID_TIMESTAMP)
  1039.                 {
  1040.                   $timeShift = $packetTS - $lastTS;
  1041.                   if ($timeShift > $this->fixWindow)
  1042.                     {
  1043.                       $this->baseTS += $timeShift - FRAMEFIX_STEP;
  1044.                       $packetTS = $lastTS + FRAMEFIX_STEP;
  1045.                     }
  1046.                 }
  1047.               if ($packetTS != $currentTS)
  1048.                   $this->WriteFlvTimestamp($frag, $fragPos, $packetTS);
  1049.  
  1050.               switch ($packetType)
  1051.               {
  1052.                   case AUDIO:
  1053.                       if ($packetTS > $this->prevAudioTS - $this->fixWindow)
  1054.                         {
  1055.                           $FrameInfo = ReadByte($frag, $fragPos + $this->tagHeaderLen);
  1056.                           $CodecID   = ($FrameInfo & 0xF0) >> 4;
  1057.                           if ($CodecID == CODEC_ID_AAC)
  1058.                             {
  1059.                               $AAC_PacketType = ReadByte($frag, $fragPos + $this->tagHeaderLen + 1);
  1060.                               if ($AAC_PacketType == AAC_SEQUENCE_HEADER)
  1061.                                 {
  1062.                                   if ($this->AAC_HeaderWritten)
  1063.                                     {
  1064.                                       LogDebug(sprintf("%s\n" . $this->format, "Skipping AAC sequence header", "AUDIO", $packetTS, $this->prevAudioTS, $packetSize), $debug);
  1065.                                       break;
  1066.                                     }
  1067.                                   else
  1068.                                     {
  1069.                                       LogDebug("Writing AAC sequence header", $debug);
  1070.                                       $this->AAC_HeaderWritten = true;
  1071.                                     }
  1072.                                 }
  1073.                               else if (!$this->AAC_HeaderWritten)
  1074.                                 {
  1075.                                   LogDebug(sprintf("%s\n" . $this->format, "Discarding audio packet received before AAC sequence header", "AUDIO", $packetTS, $this->prevAudioTS, $packetSize), $debug);
  1076.                                   break;
  1077.                                 }
  1078.                             }
  1079.                           if ($packetSize > 0)
  1080.                             {
  1081.                               // Check for packets with non-monotonic audio timestamps and fix them
  1082.                               if (!(($CodecID == CODEC_ID_AAC) and (($AAC_PacketType == AAC_SEQUENCE_HEADER) or $this->prevAAC_Header)))
  1083.                                   if (($this->prevAudioTS != INVALID_TIMESTAMP) and ($packetTS <= $this->prevAudioTS))
  1084.                                     {
  1085.                                       LogDebug(sprintf("%s\n" . $this->format, "Fixing audio timestamp", "AUDIO", $packetTS, $this->prevAudioTS, $packetSize), $debug);
  1086.                                       $packetTS += (FRAMEFIX_STEP / 5) + ($this->prevAudioTS - $packetTS);
  1087.                                       $this->WriteFlvTimestamp($frag, $fragPos, $packetTS);
  1088.                                     }
  1089.                               if (is_resource($flv))
  1090.                                 {
  1091.                                   $this->pAudioTagPos = ftell($flv);
  1092.                                   $status             = fwrite($flv, substr($frag, $fragPos, $totalTagLen), $totalTagLen);
  1093.                                   if (!$status)
  1094.                                       LogError("Failed to write flv data to file");
  1095.                                   if ($debug)
  1096.                                       LogDebug(sprintf($this->format . "%-16s", "AUDIO", $packetTS, $this->prevAudioTS, $packetSize, $this->pAudioTagPos));
  1097.                                 }
  1098.                               else
  1099.                                 {
  1100.                                   $flvData .= substr($frag, $fragPos, $totalTagLen);
  1101.                                   if ($debug)
  1102.                                       LogDebug(sprintf($this->format, "AUDIO", $packetTS, $this->prevAudioTS, $packetSize));
  1103.                                 }
  1104.                               if (($CodecID == CODEC_ID_AAC) and ($AAC_PacketType == AAC_SEQUENCE_HEADER))
  1105.                                   $this->prevAAC_Header = true;
  1106.                               else
  1107.                                   $this->prevAAC_Header = false;
  1108.                               $this->prevAudioTS  = $packetTS;
  1109.                               $this->pAudioTagLen = $totalTagLen;
  1110.                             }
  1111.                           else
  1112.                               LogDebug(sprintf("%s\n" . $this->format, "Skipping small sized audio packet", "AUDIO", $packetTS, $this->prevAudioTS, $packetSize), $debug);
  1113.                         }
  1114.                       else
  1115.                           LogDebug(sprintf("%s\n" . $this->format, "Skipping audio packet in fragment $fragNum", "AUDIO", $packetTS, $this->prevAudioTS, $packetSize), $debug);
  1116.                       if (!$this->audio)
  1117.                           $this->audio = true;
  1118.                       break;
  1119.                   case VIDEO:
  1120.                       if ($packetTS > $this->prevVideoTS - $this->fixWindow)
  1121.                         {
  1122.                           $FrameInfo = ReadByte($frag, $fragPos + $this->tagHeaderLen);
  1123.                           $FrameType = ($FrameInfo & 0xF0) >> 4;
  1124.                           $CodecID   = $FrameInfo & 0x0F;
  1125.                           if ($FrameType == FRAME_TYPE_INFO)
  1126.                             {
  1127.                               LogDebug(sprintf("%s\n" . $this->format, "Skipping video info frame", "VIDEO", $packetTS, $this->prevVideoTS, $packetSize), $debug);
  1128.                               break;
  1129.                             }
  1130.                           if ($CodecID == CODEC_ID_AVC)
  1131.                             {
  1132.                               $AVC_PacketType = ReadByte($frag, $fragPos + $this->tagHeaderLen + 1);
  1133.                               if ($AVC_PacketType == AVC_SEQUENCE_HEADER)
  1134.                                 {
  1135.                                   if ($this->AVC_HeaderWritten)
  1136.                                     {
  1137.                                       LogDebug(sprintf("%s\n" . $this->format, "Skipping AVC sequence header", "VIDEO", $packetTS, $this->prevVideoTS, $packetSize), $debug);
  1138.                                       break;
  1139.                                     }
  1140.                                   else
  1141.                                     {
  1142.                                       LogDebug("Writing AVC sequence header", $debug);
  1143.                                       $this->AVC_HeaderWritten = true;
  1144.                                     }
  1145.                                 }
  1146.                               else if (!$this->AVC_HeaderWritten)
  1147.                                 {
  1148.                                   LogDebug(sprintf("%s\n" . $this->format, "Discarding video packet received before AVC sequence header", "VIDEO", $packetTS, $this->prevVideoTS, $packetSize), $debug);
  1149.                                   break;
  1150.                                 }
  1151.                             }
  1152.                           if ($packetSize > 0)
  1153.                             {
  1154.                               // Check for packets with non-monotonic video timestamps and fix them
  1155.                               if (!(($CodecID == CODEC_ID_AVC) and (($AVC_PacketType == AVC_SEQUENCE_HEADER) or ($AVC_PacketType == AVC_SEQUENCE_END) or $this->prevAVC_Header)))
  1156.                                   if (($this->prevVideoTS != INVALID_TIMESTAMP) and ($packetTS <= $this->prevVideoTS))
  1157.                                     {
  1158.                                       LogDebug(sprintf("%s\n" . $this->format, "Fixing video timestamp", "VIDEO", $packetTS, $this->prevVideoTS, $packetSize), $debug);
  1159.                                       $packetTS += (FRAMEFIX_STEP / 5) + ($this->prevVideoTS - $packetTS);
  1160.                                       $this->WriteFlvTimestamp($frag, $fragPos, $packetTS);
  1161.                                     }
  1162.                               if (is_resource($flv))
  1163.                                 {
  1164.                                   $this->pVideoTagPos = ftell($flv);
  1165.                                   $status             = fwrite($flv, substr($frag, $fragPos, $totalTagLen), $totalTagLen);
  1166.                                   if (!$status)
  1167.                                       LogError("Failed to write flv data to file");
  1168.                                   if ($debug)
  1169.                                       LogDebug(sprintf($this->format . "%-16s", "VIDEO", $packetTS, $this->prevVideoTS, $packetSize, $this->pVideoTagPos));
  1170.                                 }
  1171.                               else
  1172.                                 {
  1173.                                   $flvData .= substr($frag, $fragPos, $totalTagLen);
  1174.                                   if ($debug)
  1175.                                       LogDebug(sprintf($this->format, "VIDEO", $packetTS, $this->prevVideoTS, $packetSize));
  1176.                                 }
  1177.                               if (($CodecID == CODEC_ID_AVC) and ($AVC_PacketType == AVC_SEQUENCE_HEADER))
  1178.                                   $this->prevAVC_Header = true;
  1179.                               else
  1180.                                   $this->prevAVC_Header = false;
  1181.                               $this->prevVideoTS  = $packetTS;
  1182.                               $this->pVideoTagLen = $totalTagLen;
  1183.                             }
  1184.                           else
  1185.                               LogDebug(sprintf("%s\n" . $this->format, "Skipping small sized video packet", "VIDEO", $packetTS, $this->prevVideoTS, $packetSize), $debug);
  1186.                         }
  1187.                       else
  1188.                           LogDebug(sprintf("%s\n" . $this->format, "Skipping video packet in fragment $fragNum", "VIDEO", $packetTS, $this->prevVideoTS, $packetSize), $debug);
  1189.                       if (!$this->video)
  1190.                           $this->video = true;
  1191.                       break;
  1192.                   case SCRIPT_DATA:
  1193.                       break;
  1194.                   default:
  1195.                       LogError("Unknown packet type " . $packetType . " encountered! Encrypted fragments can't be recovered.", 2);
  1196.               }
  1197.               $fragPos += $totalTagLen;
  1198.             }
  1199.           $this->duration = round($packetTS / 1000, 0);
  1200.           if (is_resource($flv))
  1201.             {
  1202.               $this->filesize = ftell($flv) / (1024 * 1024);
  1203.               return true;
  1204.             }
  1205.           else
  1206.               return $flvData;
  1207.         }
  1208.  
  1209.       function WriteFragment($download, &$opt)
  1210.         {
  1211.           $this->frags[$download['id']] = $download;
  1212.  
  1213.           $available = count($this->frags);
  1214.           for ($i = 0; $i < $available; $i++)
  1215.             {
  1216.               if (isset($this->frags[$this->lastFrag + 1]))
  1217.                 {
  1218.                   $frag = $this->frags[$this->lastFrag + 1];
  1219.                   if ($frag['response'] !== false)
  1220.                     {
  1221.                       LogDebug("Writing fragment " . $frag['id'] . " to flv file");
  1222.                       if (!isset($opt['file']))
  1223.                         {
  1224.                           $opt['debug'] = false;
  1225.                           if ($this->play)
  1226.                               $outFile = STDOUT;
  1227.                           else if ($this->outFile)
  1228.                             {
  1229.                               if ($opt['filesize'])
  1230.                                   $outFile = $this->outDir . $this->outFile . "-" . $this->fileCount++ . ".flv";
  1231.                               else
  1232.                                   $outFile = $this->outDir . $this->outFile . ".flv";
  1233.                             }
  1234.                           else
  1235.                             {
  1236.                               if ($opt['filesize'])
  1237.                                   $outFile = $this->outDir . $this->baseFilename . "-" . $this->fileCount++ . ".flv";
  1238.                               else
  1239.                                   $outFile = $this->outDir . $this->baseFilename . ".flv";
  1240.                             }
  1241.                           $this->InitDecoder();
  1242.                           $this->DecodeFragment($frag['response'], $frag['id'], $opt);
  1243.                           $opt['file'] = WriteFlvFile($outFile, $this->audio, $this->video);
  1244.                           if (!($this->live or ($this->fragStart > 0) or $this->filesize or $opt['tDuration']))
  1245.                               $this->WriteMetadata($opt['file']);
  1246.  
  1247.                           $opt['debug'] = $this->debug;
  1248.                           $this->InitDecoder();
  1249.                         }
  1250.                       $flvData = $this->DecodeFragment($frag['response'], $frag['id'], $opt);
  1251.                       if (strlen($flvData))
  1252.                         {
  1253.                           $status = fwrite($opt['file'], $flvData, strlen($flvData));
  1254.                           if (!$status)
  1255.                               LogError("Failed to write flv data");
  1256.                           if (!$this->play)
  1257.                               $this->filesize = ftell($opt['file']) / (1024 * 1024);
  1258.                         }
  1259.                       $this->lastFrag = $frag['id'];
  1260.                     }
  1261.                   else
  1262.                     {
  1263.                       $this->lastFrag += 1;
  1264.                       LogDebug("Skipping failed fragment " . $this->lastFrag);
  1265.                     }
  1266.                   unset($this->frags[$this->lastFrag]);
  1267.                 }
  1268.               else
  1269.                   break;
  1270.  
  1271.               if ($opt['tDuration'] and (($opt['duration'] + $this->duration) >= $opt['tDuration']))
  1272.                 {
  1273.                   LogInfo("");
  1274.                   LogInfo(($opt['duration'] + $this->duration) . " seconds of content has been recorded successfully.", true);
  1275.                   return 2;
  1276.                 }
  1277.               if ($opt['filesize'] and ($this->filesize >= $opt['filesize']))
  1278.                 {
  1279.                   $this->filesize = 0;
  1280.                   $opt['duration'] += $this->duration;
  1281.                   fclose($opt['file']);
  1282.                   unset($opt['file']);
  1283.                 }
  1284.             }
  1285.  
  1286.           if (!count($this->frags))
  1287.               unset($this->frags);
  1288.           return true;
  1289.         }
  1290.     }
  1291.  
  1292.   function ReadByte($str, $pos)
  1293.     {
  1294.       $int = unpack("C", $str[$pos]);
  1295.       return $int[1];
  1296.     }
  1297.  
  1298.   function ReadInt24($str, $pos)
  1299.     {
  1300.       $int32 = unpack("N", "\x00" . substr($str, $pos, 3));
  1301.       return $int32[1];
  1302.     }
  1303.  
  1304.   function ReadInt32($str, $pos)
  1305.     {
  1306.       $int32 = unpack("N", substr($str, $pos, 4));
  1307.       return $int32[1];
  1308.     }
  1309.  
  1310.   function ReadInt64($str, $pos)
  1311.     {
  1312.       $hi    = sprintf("%u", ReadInt32($str, $pos));
  1313.       $lo    = sprintf("%u", ReadInt32($str, $pos + 4));
  1314.       $int64 = bcadd(bcmul($hi, "4294967296"), $lo);
  1315.       return $int64;
  1316.     }
  1317.  
  1318.   function ReadString($str, &$pos)
  1319.     {
  1320.       $len = 0;
  1321.       while ($str[$pos + $len] != "\x00")
  1322.           $len++;
  1323.       $str = substr($str, $pos, $len);
  1324.       $pos += $len + 1;
  1325.       return $str;
  1326.     }
  1327.  
  1328.   function ReadBoxHeader($str, &$pos, &$boxType, &$boxSize)
  1329.     {
  1330.       if (!isset($pos))
  1331.           $pos = 0;
  1332.       $boxSize = ReadInt32($str, $pos);
  1333.       $boxType = substr($str, $pos + 4, 4);
  1334.       if ($boxSize == 1)
  1335.         {
  1336.           $boxSize = ReadInt64($str, $pos + 8) - 16;
  1337.           $pos += 16;
  1338.         }
  1339.       else
  1340.         {
  1341.           $boxSize -= 8;
  1342.           $pos += 8;
  1343.         }
  1344.       if ($boxSize <= 0)
  1345.           $boxSize = 0;
  1346.     }
  1347.  
  1348.   function WriteByte(&$str, $pos, $int)
  1349.     {
  1350.       $str[$pos] = pack("C", $int);
  1351.     }
  1352.  
  1353.   function WriteInt24(&$str, $pos, $int)
  1354.     {
  1355.       $str[$pos]     = pack("C", ($int & 0xFF0000) >> 16);
  1356.       $str[$pos + 1] = pack("C", ($int & 0xFF00) >> 8);
  1357.       $str[$pos + 2] = pack("C", $int & 0xFF);
  1358.     }
  1359.  
  1360.   function WriteInt32(&$str, $pos, $int)
  1361.     {
  1362.       $str[$pos]     = pack("C", ($int & 0xFF000000) >> 24);
  1363.       $str[$pos + 1] = pack("C", ($int & 0xFF0000) >> 16);
  1364.       $str[$pos + 2] = pack("C", ($int & 0xFF00) >> 8);
  1365.       $str[$pos + 3] = pack("C", $int & 0xFF);
  1366.     }
  1367.  
  1368.   function WriteBoxSize(&$str, $pos, $type, $size)
  1369.     {
  1370.       if (substr($str, $pos - 4, 4) == $type)
  1371.           WriteInt32($str, $pos - 8, $size);
  1372.       else
  1373.         {
  1374.           WriteInt32($str, $pos - 8, 0);
  1375.           WriteInt32($str, $pos - 4, $size);
  1376.         }
  1377.     }
  1378.  
  1379.   function GetString($xmlObject)
  1380.     {
  1381.       return trim((string) $xmlObject);
  1382.     }
  1383.  
  1384.   function isHttpUrl($url)
  1385.     {
  1386.       if (strncasecmp($url, "http", 4) == 0)
  1387.           return true;
  1388.       else
  1389.           return false;
  1390.     }
  1391.  
  1392.   function JoinUrl($firstUrl, $secondUrl)
  1393.     {
  1394.       if ($firstUrl and (substr($firstUrl, -1) == '/'))
  1395.           $firstUrl = substr($firstUrl, 0, -1);
  1396.       if ($secondUrl and (substr($secondUrl, 0, 1) == '/'))
  1397.           $secondUrl = substr($secondUrl, 1);
  1398.       return $firstUrl . "/" . $secondUrl;
  1399.     }
  1400.  
  1401.   function KeyName(array $a, $pos)
  1402.     {
  1403.       $temp = array_slice($a, $pos, 1, true);
  1404.       return key($temp);
  1405.     }
  1406.  
  1407.   function LogDebug($msg, $display = true)
  1408.     {
  1409.       global $debug, $logfile, $showHeader;
  1410.       if ($showHeader)
  1411.         {
  1412.           ShowHeader();
  1413.           $showHeader = false;
  1414.         }
  1415.       if ($display and $debug)
  1416.           fwrite($logfile, $msg . "\n");
  1417.     }
  1418.  
  1419.   function LogError($msg, $code = 1)
  1420.     {
  1421.       global $quiet;
  1422.       if (!$quiet)
  1423.           PrintLine($msg);
  1424.       exit($code);
  1425.     }
  1426.  
  1427.   function LogInfo($msg, $progress = false)
  1428.     {
  1429.       global $quiet;
  1430.       if (!$quiet)
  1431.           PrintLine($msg, $progress);
  1432.     }
  1433.  
  1434.   function NormalizePath($path)
  1435.     {
  1436.       $inSegs  = preg_split('/(?<!\/)\/(?!\/)/u', $path);
  1437.       $outSegs = array();
  1438.  
  1439.       foreach ($inSegs as $seg)
  1440.         {
  1441.           if ($seg == '' || $seg == '.')
  1442.               continue;
  1443.           if ($seg == '..')
  1444.               array_pop($outSegs);
  1445.           else
  1446.               array_push($outSegs, $seg);
  1447.         }
  1448.       $outPath = implode('/', $outSegs);
  1449.  
  1450.       if (substr($path, 0, 1) == '/')
  1451.           $outPath = '/' . $outPath;
  1452.       if (substr($path, -1) == '/')
  1453.           $outPath .= '/';
  1454.       return $outPath;
  1455.     }
  1456.  
  1457.   function PrintLine($msg, $progress = false)
  1458.     {
  1459.       global $showHeader;
  1460.       if ($showHeader)
  1461.         {
  1462.           ShowHeader();
  1463.           $showHeader = false;
  1464.         }
  1465.       if ($msg)
  1466.         {
  1467.           printf("\r%-79s\r", "");
  1468.           if ($progress)
  1469.               printf("%s\r", $msg);
  1470.           else
  1471.               printf("%s\n", $msg);
  1472.         }
  1473.       else
  1474.           printf("\n");
  1475.     }
  1476.  
  1477.   function RemoveExtension($outFile)
  1478.     {
  1479.       preg_match("/\.\w{1,4}$/i", $outFile, $extension);
  1480.       if (isset($extension[0]))
  1481.         {
  1482.           $extension = $extension[0];
  1483.           $outFile   = substr($outFile, 0, -strlen($extension));
  1484.           return $outFile;
  1485.         }
  1486.       return $outFile;
  1487.     }
  1488.  
  1489.   function ShowHeader()
  1490.     {
  1491.       $header = "KSV Adobe HDS Downloader";
  1492.       $len    = strlen($header);
  1493.       $width  = (int) ((80 - $len) / 2) + $len;
  1494.       $format = "\n%" . $width . "s\n\n";
  1495.       printf($format, $header);
  1496.     }
  1497.  
  1498.   function WriteFlvFile($outFile, $audio = true, $video = true)
  1499.     {
  1500.       $flvHeader    = pack("H*", "464c5601050000000900000000");
  1501.       $flvHeaderLen = strlen($flvHeader);
  1502.  
  1503.       if (!($audio and $video))
  1504.         {
  1505.           if ($audio and !$video)
  1506.               $flvHeader[4] = "\x04";
  1507.           else if ($video and !$audio)
  1508.               $flvHeader[4] = "\x01";
  1509.         }
  1510.  
  1511.       if (is_resource($outFile))
  1512.           $flv = $outFile;
  1513.       else
  1514.           $flv = fopen($outFile, "w+b");
  1515.       if (!$flv)
  1516.           LogError("Failed to open " . $outFile);
  1517.       fwrite($flv, $flvHeader, $flvHeaderLen);
  1518.       return $flv;
  1519.     }
  1520.  
  1521.   function in_array_field($needle, $needle_field, $haystack, $strict = false)
  1522.     {
  1523.       if ($strict)
  1524.         {
  1525.           foreach ($haystack as $item)
  1526.               if (isset($item[$needle_field]) && $item[$needle_field] === $needle)
  1527.                   return true;
  1528.         }
  1529.       else
  1530.         {
  1531.           foreach ($haystack as $item)
  1532.               if (isset($item[$needle_field]) && $item[$needle_field] == $needle)
  1533.                   return true;
  1534.         }
  1535.       return false;
  1536.     }
  1537.  
  1538.   function value_in_array_field($needle, $needle_field, $value_field, $haystack, $strict = false)
  1539.     {
  1540.       if ($strict)
  1541.         {
  1542.           foreach ($haystack as $item)
  1543.               if (isset($item[$needle_field]) && $item[$needle_field] === $needle)
  1544.                   return $item[$value_field];
  1545.         }
  1546.       else
  1547.         {
  1548.           foreach ($haystack as $item)
  1549.               if (isset($item[$needle_field]) && $item[$needle_field] == $needle)
  1550.                   return $item[$value_field];
  1551.         }
  1552.       return false;
  1553.     }
  1554.  
  1555.   // Global code starts here
  1556.   $format       = " %-8s%-16s%-16s%-8s";
  1557.   $baseFilename = "";
  1558.   $debug        = false;
  1559.   $duration     = 0;
  1560.   $delete       = false;
  1561.   $fileExt      = ".f4f";
  1562.   $fileCount    = 1;
  1563.   $filesize     = 0;
  1564.   $fixWindow    = 1000;
  1565.   $fragCount    = 0;
  1566.   $fragNum      = 0;
  1567.   $logfile      = STDERR;
  1568.   $manifest     = "";
  1569.   $outDir       = "";
  1570.   $outFile      = "";
  1571.   $play         = false;
  1572.   $quiet        = false;
  1573.   $referrer     = "";
  1574.   $rename       = false;
  1575.   $showHeader   = true;
  1576.   $start        = 0;
  1577.   $update       = false;
  1578.  
  1579.   // Set large enough memory limit
  1580.   ini_set("memory_limit", "512M");
  1581.  
  1582.   // Check if STDOUT is available
  1583.   $cli = new CLI();
  1584.   if ($cli->getParam('play'))
  1585.     {
  1586.       $play       = true;
  1587.       $quiet      = true;
  1588.       $showHeader = false;
  1589.     }
  1590.   if ($cli->getParam('help'))
  1591.     {
  1592.       $cli->displayHelp();
  1593.       exit(0);
  1594.     }
  1595.  
  1596.   // Check for required extensions
  1597.   $extensions = array(
  1598.       "bcmath",
  1599.       "curl",
  1600.       "SimpleXML"
  1601.   );
  1602.   foreach ($extensions as $extension)
  1603.       if (!extension_loaded($extension))
  1604.           LogError("You don't have '$extension' extension installed. please install it before continuing.");
  1605.  
  1606.   // Initialize classes
  1607.   $cc  = new cURL();
  1608.   $f4f = new F4F();
  1609.  
  1610.   $f4f->baseFilename =& $baseFilename;
  1611.   $f4f->debug =& $debug;
  1612.   $f4f->fixWindow =& $fixWindow;
  1613.   $f4f->format =& $format;
  1614.   $f4f->outDir =& $outDir;
  1615.   $f4f->outFile =& $outFile;
  1616.   $f4f->play =& $play;
  1617.   $f4f->rename =& $rename;
  1618.  
  1619.   // Process command line options
  1620.   if ($cli->getParam('debug'))
  1621.       $debug = true;
  1622.   if ($cli->getParam('delete'))
  1623.       $delete = true;
  1624.   if ($cli->getParam('fproxy'))
  1625.       $cc->fragProxy = true;
  1626.   if ($cli->getParam('rename'))
  1627.       $rename = $cli->getParam('rename');
  1628.   if ($cli->getParam('update'))
  1629.       $update = true;
  1630.   if ($cli->getParam('auth'))
  1631.       $f4f->auth = "?" . $cli->getParam('auth');
  1632.   if ($cli->getParam('duration'))
  1633.       $duration = $cli->getParam('duration');
  1634.   if ($cli->getParam('filesize'))
  1635.       $filesize = $cli->getParam('filesize');
  1636.   if ($cli->getParam('fixwindow'))
  1637.       $fixWindow = $cli->getParam('fixwindow');
  1638.   if ($cli->getParam('fragments'))
  1639.       $baseFilename = $cli->getParam('fragments');
  1640.   if ($cli->getParam('manifest'))
  1641.       $manifest = $cli->getParam('manifest');
  1642.   if ($cli->getParam('outdir'))
  1643.       $outDir = $cli->getParam('outdir');
  1644.   if ($cli->getParam('outfile'))
  1645.       $outFile = $cli->getParam('outfile');
  1646.   if ($cli->getParam('parallel'))
  1647.       $f4f->parallel = $cli->getParam('parallel');
  1648.   if ($cli->getParam('proxy'))
  1649.       $cc->proxy = $cli->getParam('proxy');
  1650.   if ($cli->getParam('quality'))
  1651.       $f4f->quality = $cli->getParam('quality');
  1652.   if ($cli->getParam('referrer'))
  1653.       $referrer = $cli->getParam('referrer');
  1654.   if ($cli->getParam('start'))
  1655.       $start = $cli->getParam('start');
  1656.   if ($cli->getParam('useragent'))
  1657.       $cc->user_agent = $cli->getParam('useragent');
  1658.  
  1659.   // Use custom referrer
  1660.   if ($referrer)
  1661.       $cc->headers[] = "Referer: " . $referrer;
  1662.  
  1663.   // Update the script
  1664.   if ($update)
  1665.     {
  1666.       $cc->cert_check = false;
  1667.       $status         = $cc->get("https://raw.github.com/K-S-V/Scripts/master/AdobeHDS.php");
  1668.       if ($status == 200)
  1669.         {
  1670.           if (md5($cc->response) == md5(file_get_contents($argv[0])))
  1671.               LogError("You are already using the latest version of this script.", 0);
  1672.           $status = file_put_contents($argv[0], $cc->response);
  1673.           if (!$status)
  1674.               LogError("Failed to write script file");
  1675.           LogError("Script has been updated successfully.", 0);
  1676.         }
  1677.       else
  1678.           LogError("Failed to update script");
  1679.     }
  1680.  
  1681.   // Create output directory
  1682.   if ($outDir)
  1683.     {
  1684.       $outDir = rtrim(str_replace('\\', '/', $outDir));
  1685.       if (substr($outDir, -1) != '/')
  1686.           $outDir = $outDir . '/';
  1687.       if (!file_exists($outDir))
  1688.         {
  1689.           LogDebug("Creating destination directory " . $outDir);
  1690.           if (!mkdir($outDir, 0777, true))
  1691.               LogError("Failed to create destination directory " . $outDir);
  1692.         }
  1693.     }
  1694.  
  1695.   // Remove existing file extension
  1696.   if ($outFile)
  1697.       $outFile = RemoveExtension($outFile);
  1698.  
  1699.   // Disable filesize when piping
  1700.   if ($play)
  1701.       $filesize = 0;
  1702.  
  1703.   // Download fragments when manifest is available
  1704.   if ($manifest)
  1705.     {
  1706.       if (!isHttpUrl($manifest))
  1707.           $manifest = "http://" . $manifest;
  1708.       $opt = array(
  1709.           'start' => $start,
  1710.           'tDuration' => $duration,
  1711.           'filesize' => $filesize
  1712.       );
  1713.       $f4f->DownloadFragments($cc, $manifest, $opt);
  1714.     }
  1715.  
  1716.   // Determine output filename
  1717.   if (!$outFile)
  1718.     {
  1719.       $baseFilename = str_replace('\\', '/', $baseFilename);
  1720.       $lastChar     = substr($baseFilename, -1);
  1721.       if ($baseFilename and !(($lastChar == '/') or ($lastChar == ':')))
  1722.         {
  1723.           $lastSlash = strrpos($baseFilename, '/');
  1724.           if ($lastSlash)
  1725.               $outFile = substr($baseFilename, $lastSlash + 1);
  1726.           else
  1727.               $outFile = $baseFilename;
  1728.         }
  1729.       else
  1730.           $outFile = "Joined";
  1731.       $outFile = RemoveExtension($outFile);
  1732.     }
  1733.  
  1734.   // Check for available fragments and rename if required
  1735.   if ($f4f->fragNum)
  1736.       $fragNum = $f4f->fragNum;
  1737.   else if ($start)
  1738.       $fragNum = $start - 1;
  1739.   if ($rename)
  1740.     {
  1741.       $f4f->RenameFragments($baseFilename, $fragNum, $fileExt);
  1742.       $fragNum = 0;
  1743.     }
  1744.   $count = $fragNum + 1;
  1745.   while (true)
  1746.     {
  1747.       if (file_exists($baseFilename . $count . $fileExt))
  1748.           $fragCount++;
  1749.       else if (file_exists($baseFilename . $count))
  1750.         {
  1751.           $fileExt = "";
  1752.           $fragCount++;
  1753.         }
  1754.       else
  1755.           break;
  1756.       $count++;
  1757.     }
  1758.   LogInfo("Found $fragCount fragments");
  1759.  
  1760.   if (!$f4f->processed)
  1761.     {
  1762.       // Process available fragments
  1763.       if (!$fragCount)
  1764.           exit(1);
  1765.       $timeStart = microtime(true);
  1766.       LogDebug("Joining Fragments:");
  1767.       for ($i = $fragNum + 1; $i <= $fragNum + $fragCount; $i++)
  1768.         {
  1769.           $frag = file_get_contents($baseFilename . $i . $fileExt);
  1770.           if (!isset($opt['flv']))
  1771.             {
  1772.               $opt['debug'] = false;
  1773.               $f4f->InitDecoder();
  1774.               $f4f->DecodeFragment($frag, $i, $opt);
  1775.               if ($filesize)
  1776.                   $opt['flv'] = WriteFlvFile($outDir . $outFile . "-" . $fileCount++ . ".flv", $f4f->audio, $f4f->video);
  1777.               else
  1778.                   $opt['flv'] = WriteFlvFile($outDir . $outFile . ".flv", $f4f->audio, $f4f->video);
  1779.               if (!(($fragNum > 0) or $filesize))
  1780.                   $f4f->WriteMetadata($opt['flv']);
  1781.  
  1782.               $opt['debug'] = $debug;
  1783.               $f4f->InitDecoder();
  1784.             }
  1785.           $f4f->DecodeFragment($frag, $i, $opt);
  1786.           if ($filesize and ($f4f->filesize >= $filesize))
  1787.             {
  1788.               $f4f->filesize = 0;
  1789.               fclose($opt['flv']);
  1790.               unset($opt['flv']);
  1791.             }
  1792.           LogInfo("Processed " . ($i - $fragNum) . " fragments", true);
  1793.         }
  1794.       fclose($opt['flv']);
  1795.       $timeEnd   = microtime(true);
  1796.       $timeTaken = sprintf("%.2f", $timeEnd - $timeStart);
  1797.       LogInfo("Joined $fragCount fragments in $timeTaken seconds");
  1798.     }
  1799.  
  1800.   // Delete fragments after processing
  1801.   if ($delete)
  1802.     {
  1803.       for ($i = $fragNum + 1; $i <= $fragNum + $fragCount; $i++)
  1804.           if (file_exists($baseFilename . $i . $fileExt))
  1805.               unlink($baseFilename . $i . $fileExt);
  1806.     }
  1807.  
  1808.   LogInfo("Finished");
  1809. ?>
Add Comment
Please, Sign In to add comment