View difference between Paste ID: SjbASZ1U and 2FRzDs15
SHOW: | | - or go back to the newest paste.
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
?>