PageRenderTime 88ms CodeModel.GetById 53ms RepoModel.GetById 0ms app.codeStats 0ms

/rescene.php

https://bitbucket.org/Gfy/php-rescene
PHP | 2862 lines | 1930 code | 312 blank | 620 comment | 304 complexity | 33ade3b945d705a1d5ad146e49485ffa MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * PHP Library to read and edit a .srr file. It reads .srs files.
  4. * Copyright (c) 2011-2017 Gfy
  5. *
  6. * rescene.php is free software, you can redistribute it and/or modify
  7. * it under the terms of GNU Affero General Public License
  8. * as published by the Free Software Foundation, either version 3
  9. * of the License, or (at your option) any later version.
  10. *
  11. * You should have received a copy of the the GNU Affero
  12. * General Public License, along with rescene.php. If not, see
  13. * http://www.gnu.org/licenses/agpl.html
  14. *
  15. * Additional permission under the GNU Affero GPL version 3 section 7:
  16. *
  17. * If you modify this Program, or any covered work, by linking or
  18. * combining it with other code, such other code is not for that reason
  19. * alone subject to any of the requirements of the GNU Affero GPL
  20. * version 3.
  21. */
  22. /*
  23. * LGPLv3 with Affero clause (LAGPL)
  24. * See http://mo.morsi.org/blog/node/270
  25. * rescene.php written on 2011-07-27
  26. * Last version: 2017-07-09
  27. *
  28. * Features:
  29. * - process a SRR file which returns:
  30. * - SRR file size.
  31. * - Application name of the tool used to create the SRR file.
  32. * - List of files stored in the SRR.
  33. * - List of RAR volumes the SRR can reconstruct.
  34. * - List of files that are archived inside these RARs.
  35. * - Size of all Recovery Records inside the SRR file.
  36. * - Comments inside SFV files.
  37. * - Warnings when something unusual is found with the SRR.
  38. * - Remove a stored file.
  39. * - Rename a stored file.
  40. * - Add a stored file.
  41. * - Read a stored file.
  42. * - Extract a stored file.
  43. * - Calculate a hash of the SRR based on RAR metadata.
  44. * - Sorting of the stored file names.
  45. * - process in memory SRR 'file'
  46. * - compare two SRR files
  47. * - nfo: ignore line endings
  48. * - sfv: sort it before comparing and remove comment lines
  49. * - rar metadata
  50. * -> quick: by hash
  51. * -> see what is missing
  52. * - other files
  53. * -> quick: by hash
  54. * - compare SRS files
  55. * - Output flag added to indicate if the RARs used compression.
  56. * - Support to read SRS files. (AVI/MKV/MP4/WMV/FLAC/MP3)
  57. * - Sort stored files inside the SRR.
  58. * - OpenSubtitles.org hash support.
  59. * - Extract the SRR meta data of a single RAR set
  60. *
  61. * - nfo compare: strip line endings + new line?
  62. * Indiana.Jones.And.The.Last.Crusade.1989.PAL.DVDR-DNA
  63. *
  64. * List of possible features/todo list:
  65. * - process in memory SRR 'file' + other API functions (very low priority)
  66. * => can be done using temp files in memory
  67. * - refactor compare SRR
  68. * - merge SRRs (Python script exists)
  69. * - encryption sanity check
  70. * - add paths before the rar files
  71. * - detect when SRR is cut/metadata from rars missing
  72. * => hard to do correctly (SFVs subs exist too)
  73. * - how to throw errors correctly?
  74. * - sorting the list of the stored files by hand
  75. * - "Application name found in the middle of the SRR."
  76. * causes hashes to be different
  77. * - http://www.srrdb.com/release/details/Race.To.Witch.Mountain.1080p.BluRay.x264-HD1080 (wrong file size)
  78. * - http://www.srrdb.com/release/details/NBA.2010.03.02.Pacers.Vs.Lakers.720p.HDTV.x264-BALLS (crc FFFFFFFF)
  79. * - http://www.srrdb.com/release/details/Dexter.S01E03.720p.Bluray.x264-ORPHEUS (short crc)
  80. * - When renaming a file and only the capitals will be different, a file with the old name is added.
  81. * - http://www.srrdb.com/release/details/Scrapland.AlcoholClone.MI-NOGRP (dupe names, so not all files get shown)
  82. * - Add error when a file is twice in the SFV (twice the meta data too)
  83. * - Add warning when there is no SFV file and the SRR contains RAR meta data
  84. *
  85. */
  86. // necessary for storing files in large (60MB) SRR files
  87. ini_set('memory_limit', '512M');
  88. $BLOCKNAME = array(
  89. 0x69 => 'SRR VolumeHeader',
  90. 0x6A => 'SRR Stored File',
  91. 0x6B => 'SRR OSO Hash',
  92. 0x6C => 'SRR RAR Padding',
  93. 0x71 => 'SRR RAR subblock',
  94. 0x72 => 'RAR Marker',
  95. 0x73 => 'Archive Header',
  96. 0x74 => 'File',
  97. 0x75 => 'Old style - Comment',
  98. 0x76 => 'Old style - Extra info (authenticity information)',
  99. 0x77 => 'Old style - Subblock',
  100. 0x78 => 'Old style - Recovery record',
  101. 0x79 => 'Old style - Archive authenticity',
  102. 0x7A => 'New-format subblock',
  103. 0x7B => 'Archive end'
  104. );
  105. class FileType {
  106. const MKV = 'MKV';
  107. const AVI = 'AVI';
  108. const MP4 = 'MP4';
  109. const WMV = 'WMV';
  110. const FLAC = 'FLAC';
  111. const MP3 = 'MP3';
  112. const STREAM = 'STRM'; // vob and basic m2ts
  113. const M2TS = 'M2TS';
  114. const Unknown = '';
  115. }
  116. // for suppressing error messages
  117. $CLI_APP = false;
  118. // cli progs are cool
  119. if (!empty($argc) && strstr($argv[0], basename(__FILE__))) {
  120. $CLI_APP = true;
  121. /* How to use the CLI version in Windows:
  122. - Download and install PHP. http://windows.php.net/download/
  123. - Run this script by entering something like
  124. C:\Program Files (x86)\PHP\php.exe rescene.php
  125. in the command prompt.
  126. - [Add 'C:\Program Files (x86)\PHP' to your systems Path environment variable
  127. to be able to run PHP from anywhere.]
  128. - To run this script from everywhere, create 'rescene.bat' in a directory that is in your PATH.
  129. For example: 'C:\Windows\rescene.bat'
  130. Include the following content:
  131. "C:\Program Files (x86)\PHP\php.exe" "C:\Windows\rescene.php" %*
  132. And place the PHP file accordingly.
  133. Enter 'rescene' anywhere to use it.
  134. */
  135. if (!array_key_exists(1, $argv)) {
  136. echo "The first parameter needs to be a .srr file.\n";
  137. echo " -s 'file to store' (Save)\n";
  138. echo " -d 'file to remove' (Delete)\n";
  139. echo " -r 'file to rename' (Rename)\n";
  140. echo " -v 'file to get' (View)\n";
  141. echo " -x 'file to write' (eXtract)\n";
  142. echo " -p 'file to split' (sPlit)\n";
  143. echo " -h 'special hash of the SRR file' (Hash)\n";
  144. echo " -a 'show SRS info (sAmple)\n";
  145. echo " -l 'show stored SRR languages (Languages)\n";
  146. echo " -c 'compare two SRR files' (Compare)\n";
  147. echo " -t 'runs a couple of small tests' (Testing)\n";
  148. exit(1);
  149. }
  150. $srr = $argv[1];
  151. // to test execution time
  152. $mtime = microtime();
  153. $mtime = explode(' ',$mtime);
  154. $mtime = $mtime[1] + $mtime[0];
  155. $starttime = $mtime;
  156. if (array_key_exists(2, $argv)) {
  157. $switch = $argv[2];
  158. if (array_key_exists(3, $argv)) {
  159. $file = $argv[3];
  160. switch($switch) {
  161. case '-d': // delete
  162. if (removeFile($srr, $file)) {
  163. echo 'File successfully removed.';
  164. } else {
  165. echo 'File not found in SRR file.';
  166. }
  167. break;
  168. case '-s': // store
  169. $path = '';
  170. if (array_key_exists(4, $argv)) {
  171. $path = $argv[4];
  172. }
  173. if (storeFileCli($srr, $file, $path)) {
  174. echo 'File successfully stored.';
  175. } else {
  176. echo 'Error while storing file.';
  177. }
  178. break;
  179. case '-r': // rename
  180. if (array_key_exists(4, $argv)) {
  181. $newName = $argv[4];
  182. echo 'SRR file: ' . $srr . "\n";
  183. echo 'Old name: ' . $file . "\n";
  184. echo 'New name: ' . $newName . "\n";
  185. if (renameFile($srr, $file, $newName)) {
  186. echo 'File successfully renamed.';
  187. } else {
  188. echo 'Error while renaming file.';
  189. }
  190. } else {
  191. echo 'Please enter a new name.';
  192. }
  193. break;
  194. case '-v': // view
  195. print_r(getStoredFile($srr, $file));
  196. break;
  197. case '-x': // extract
  198. // strip the path info
  199. $nopath = basename($file);
  200. $result = file_put_contents($nopath, getStoredFile($srr, $file));
  201. if ($result !== FALSE) {
  202. echo 'File succesfully extracted';
  203. } else {
  204. echo 'Something went wrong. Did you provide a correct file name with path?';
  205. }
  206. break;
  207. case '-c': // compare
  208. print_r(compareSrr($srr, $file));
  209. break;
  210. case '-p': // split
  211. $data = grabSrrSubset($srr, $file);
  212. file_put_contents('rescene.php_split.srr', $data);
  213. break;
  214. default:
  215. echo 'Unknown parameter. Use -r, -a, -v, -x or -c.';
  216. }
  217. } elseif ($switch === '-h') {
  218. echo 'The calculated content hash for this SRR file is: ';
  219. $result = processSrr($srr);
  220. echo calculateHash($srr, $result['rarFiles']);
  221. } elseif ($switch === '-a') {
  222. // show SRS info
  223. $srsData = file_get_contents($srr);
  224. print_r(processSrsData($srsData));
  225. } elseif ($switch === '-l') {
  226. // show vobsub languages
  227. print_r(getVobsubLanguages($srr));
  228. } elseif ($switch === '-t') {
  229. echo 'fileNameCheckTest: ';
  230. if (fileNameCheckTest()) {
  231. echo "OK!\n";
  232. } else {
  233. echo "NOT OK!\n";
  234. }
  235. echo 'createSrrHeaderBlockTest: ';
  236. if (createSrrHeaderBlockTest()) {
  237. echo "OK!\n";
  238. } else {
  239. echo "NOT OK!\n";
  240. }
  241. echo 'getBasenameVolumeTest: ';
  242. if (getBasenameVolumeTest()) {
  243. echo "OK!\n";
  244. } else {
  245. echo "NOT OK!\n";
  246. }
  247. //compareSrr($srr, $srr);
  248. //$data = file_get_contents($srr);
  249. //print_r(processSrrData($data));
  250. //add file
  251. //storeFileCli($srr, 'dbmodel.png');
  252. //remove file
  253. //if(removeFile($srr, 'dbmodel.png')) {
  254. // print_r("successfully removed");
  255. //}
  256. //process file
  257. // if ($result = processSrr($srr)) {
  258. // print_r($result);
  259. // echo 'success';
  260. // } else {
  261. // echo 'failure';
  262. // }
  263. // $sf = array_keys($result['storedFiles']);
  264. // sort($sf);
  265. // if (sortStoredFiles($srr, $sf)) {
  266. // echo 'success';
  267. // } else {
  268. // echo 'failure';
  269. // }
  270. }
  271. } else {
  272. $result = processSrr($srr);
  273. //print_r($result['storedFiles']);
  274. //print_r(($result['warnings']));
  275. //print_r(sortStoredFiles($result['storedFiles']));
  276. print_r($result);
  277. }
  278. // end part processing time
  279. $mtime = microtime();
  280. $mtime = explode(' ',$mtime);
  281. $mtime = $mtime[1] + $mtime[0];
  282. $endtime = $mtime;
  283. $totaltime = ($endtime - $starttime);
  284. echo "\nFile processed in {$totaltime} seconds";
  285. }
  286. // API functions
  287. /**
  288. * Processes a whole SRR file and returns an array with useful details.
  289. * @param string $file location to the file that needs to be read.
  290. * @return mixed data array, or false on failure
  291. */
  292. function processSrr($file) {
  293. $result = FALSE;
  294. if(file_exists($file)) {
  295. $fh = fopen($file, 'rb');
  296. if (flock($fh, LOCK_SH)) {
  297. $result = processSrrHandle($fh);
  298. flock($fh, LOCK_UN); // release the lock
  299. }
  300. fclose($fh); // close the file
  301. }
  302. return $result;
  303. }
  304. /**
  305. * Processes a whole SRR file and returns an array with useful details.
  306. * @param bytes $srrFileData the contents of the SRR file.
  307. */
  308. function processSrrData(&$srrFileData) {
  309. // http://www.php.net/manual/en/wrappers.php.php
  310. // Set the limit to 5 MiB. After this limit, a temporary file will be used.
  311. $memoryLimit = 5 * 1024 * 1024;
  312. $fp = fopen("php://temp/maxmemory:$memoryLimit", 'r+');
  313. fwrite($fp, $srrFileData);
  314. rewind($fp);
  315. $result = processSrrHandle($fp);
  316. fclose($fp);
  317. return $result;
  318. }
  319. /**
  320. * Leaves the file handle open!
  321. * Only used in the 2 functions above.
  322. */
  323. function processSrrHandle($fileHandle) {
  324. // global $BLOCKNAME;
  325. $fh = $fileHandle;
  326. $srrSize = getFileSizeHandle($fileHandle);
  327. // variables to store all resulting data
  328. $appName = 'No SRR application name found';
  329. $stored_files = array();
  330. $rar_files = array();
  331. $archived_files = array();
  332. $oso_hashes = array();
  333. $recovery = NULL;
  334. $sfv = array();
  335. $sfv['comments'] = array();
  336. $sfv['files'] = array();
  337. $warnings = array();
  338. $compressed = FALSE; // it's an SRR file for compressed RARs
  339. $encrypted = FALSE; // encryption is used on one or more files
  340. // other initializations
  341. $read = 0; // number of bytes we have read so far
  342. $last_read = 0; // to prevent looping on encountering bad data
  343. $current_rar = NULL;
  344. $customPacker = FALSE; // when not created with WinRAR
  345. while($read < $srrSize) {
  346. $add_size = TRUE;
  347. // to read basic block header
  348. $block = new Block($fh, $warnings);
  349. // echo 'Block type: ' . $BLOCKNAME[$block->blockType] . "\n";
  350. // echo 'Block flags: ' . dechex($block->flags) . "\n";
  351. // echo 'Header size: ' . $block->hsize . "\n";
  352. switch($block->blockType) {
  353. case 0x69: // SRR Header Block
  354. if ($appName !== 'No SRR application name found') {
  355. array_push($warnings, 'Application name found in the middle of the SRR.');
  356. }
  357. $appName = $block->readSrrAppName();
  358. break;
  359. case 0x6B: // SRR OSO Hash (blocks at the end of the file)
  360. $block->srrOsoHashFileHeader();
  361. $entry = array();
  362. $entry['fileName'] = $block->fileName;
  363. $entry['osoHash'] = $block->osoHash;
  364. $entry['fileSize'] = $block->fileSize;
  365. $entry['blockOffset'] = $block->startOffset;
  366. $entry['blockSize'] = $block->hsize;
  367. $entry['data'] = $block->data;
  368. array_push($oso_hashes, $entry);
  369. if (!is_null($current_rar)) {
  370. $current_rar = NULL; // SRR block detected: start again
  371. }
  372. break;
  373. case 0x6C: // SRR RAR Padding Block
  374. $current_rar['fileSize'] -= $block->hsize;
  375. $block->skipBlock();
  376. break;
  377. case 0x6A: // SRR Stored File Block
  378. $block->srrReadStoredFileHeader();
  379. // store stored file details
  380. $sf = array();
  381. $sf['fileName'] = $block->fileName;
  382. $sf['fileOffset'] = $block->storedFileStartOffset;
  383. $sf['fileSize'] = $block->addSize;
  384. $sf['blockOffset'] = $block->startOffset;
  385. // The same file can be stored multiple times.
  386. // This can make SRR files unnoticeably large.
  387. if (array_key_exists($block->fileName, $stored_files)) {
  388. // message here must start with Duplicate for the check in sorting
  389. array_push($warnings, "Duplicate file detected! {$sf['fileName']}");
  390. }
  391. if (preg_match('/\\\\/', $block->fileName)) {
  392. array_push($warnings, "Backslash detected! {$sf['fileName']}");
  393. }
  394. if ($block->addSize === 0) {
  395. // an "empty" directory is allowed
  396. if (strpos($sf['fileName'], '/') === FALSE) {
  397. array_push($warnings, "Empty file detected! {$sf['fileName']}");
  398. }
  399. } elseif (strtolower(substr($sf['fileName'], - 4)) === '.sfv') {
  400. // we read the sfv file to grab the crc data of the rar files
  401. $temp = processSfv(fread($fh, $block->addSize));
  402. $sfv['comments'] = array_merge($sfv['comments'], $temp['comments']);
  403. $sfv['files'] = array_merge($sfv['files'], $temp['files']);
  404. $sf['basenameVolume'] = getBasenameVolume($block->fileName, FALSE);
  405. }
  406. $block->skipBlock();
  407. // calculate CRC of the stored file
  408. $sdata = stream_get_contents($fileHandle, $block->addSize, $block->storedFileStartOffset);
  409. $sf['fileCrc'] = strtoupper(str_pad(dechex(crc32($sdata)), 8, '0', STR_PAD_LEFT));
  410. // $sf['fileCrc'] = dechex(crc32(fread($fh, $block->addSize)));
  411. // $sf['fileCrc'] = hash('crc32b', fread($fh, $block->addSize));
  412. $stored_files[$block->fileName] = $sf;
  413. // end file size counting (_should_ not be necessary for Stored File Block)
  414. // -> 'ReScene Database Cleanup Script 1.0' SRRs were fixed with 'FireScene Cleanup'
  415. // (stored files weren't before the first SRR Rar file block)
  416. case 0x71: // SRR Rar File
  417. if (!is_null($current_rar)) {
  418. $current_rar = NULL; // SRR block detected: start again
  419. }
  420. // end fall through from SRR Stored File block
  421. if ($block->blockType == 0x6A) {
  422. break;
  423. }
  424. $add_size = FALSE;
  425. // read the name of the stored rar file
  426. $block->srrReadRarFileHeader();
  427. $recovery_data_removed = $block->flags & 0x1;
  428. // the hashmap key is only the lower case file name without the path
  429. // to make it possible to add the CRC data from the SFVs
  430. $key = strtolower(basename($block->rarName));
  431. if (array_key_exists($key, $rar_files)) {
  432. $f = $rar_files[$key];
  433. } else {
  434. $f = array(); // array that stores the file details
  435. $f['fileName'] = $block->rarName; // the path is still stored here
  436. $f['fileSize'] = 0;
  437. // when the SRR is build without SFV or the SFV is missing some lines
  438. $f['fileCrc'] = 'UNKNOWN!';
  439. // useful for actually comparing srr data
  440. $f['offsetStartSrr'] = $block->startOffset; // where the SRR block begins
  441. $f['offsetStartRar'] = ftell($fh); // where the actual RAR headers begin
  442. // initialize, set later when volume header is available
  443. $f['basenameVolume'] = '';
  444. }
  445. $rar_files[$key] = $f;
  446. // start counting file size
  447. $current_rar = $f;
  448. break;
  449. case 0x74: // RAR Packed File
  450. $block->rarReadPackedFileHeader();
  451. if (array_key_exists($block->fileName, $archived_files)) {
  452. $f = $archived_files[$block->fileName];
  453. // FLEET, AVS,... (first and last rar have correct size)
  454. if ($f['fileSizeStart'] !== $block->fileSize) {
  455. $customPacker = TRUE;
  456. }
  457. } else { // new file found in the archives
  458. $f = array();
  459. $f['fileName'] = $block->fileName;
  460. $f['fileTime'] = date("Y-m-d h:i:s", $block->fileTime);
  461. $f['compressionMethod'] = $block->compressionMethod;
  462. $f['fileSizeStart'] = $block->fileSize;
  463. // file size complexity because of crappy custom packers
  464. if ($block->fileSize !== 0xffffffffffffffff && // 1
  465. $block->fileSize !== -1 && /* 2) 32 bit php */
  466. $block->fileSize !== 0xffffffff /* 2) 64 bit php */) {
  467. // file size normal case
  468. $f['fileSize'] = $block->fileSize;
  469. } else {
  470. $f['fileSize'] = 0;
  471. // 1) custom RAR packers used: last RAR contains the size
  472. // Street.Fighter.V-RELOADED or Magic.Flute-HI2U or 0x0007
  473. if ($block->fileSize == 0xffffffffffffffff) {
  474. array_push($warnings, "RELOADED/HI2U/0x0007 custom RAR packer detected.");
  475. }
  476. // 2) crap group that doesn't store the correct size at all:
  477. // The.Powerpuff.Girls.2016.S01E08.HDTV.x264-QCF
  478. if ($block->fileSize == 0xffffffff || $block->fileSize == -1) {
  479. array_push($warnings, "Crappy QCF RAR packer detected.");
  480. }
  481. }
  482. }
  483. // check if compression was used
  484. if ($f['compressionMethod'] != 0x30) { // 0x30: Storing
  485. $compressed = TRUE;
  486. }
  487. // file size counting fixes
  488. // 2) above int was correct? it must match at the end - QCF
  489. if (($block->fileSize == -1 || $block->fileSize == 0xffffffff) && !$compressed) {
  490. $f['fileSize'] += $block->addSize;
  491. }
  492. // 1) expected the last RAR (first with the proper value)
  493. if ($block->fileSize !== 0xffffffffffffffff && $f['fileSize'] == 0) {
  494. $f['fileSize'] = $block->fileSize;
  495. }
  496. // CRC of the file is the CRC stored in the last archive that has the file
  497. // add leading zeros when the CRC isn't 8 characters
  498. $f['fileCrc'] = strtoupper(str_pad($block->fileCrc, 8, '0', STR_PAD_LEFT));
  499. $archived_files[$block->fileName] = $f;
  500. // file is encrypted with password
  501. // The.Sims.4.City.Living.INTERNAL-RELOADED
  502. if ($block->flags & 0x4) {
  503. $encrypted = TRUE;
  504. }
  505. break;
  506. case 0x78: // RAR Old Recovery
  507. if (is_null($recovery)) {
  508. // first recovery block we see
  509. $recovery = array();
  510. $recovery['fileName'] = 'Protect!';
  511. $recovery['fileSize'] = 0;
  512. }
  513. $recovery['fileSize'] += $block->addSize;
  514. if ($recovery_data_removed) {
  515. $block->skipHeader();
  516. } else { // we need to skip the data that is still there
  517. $block->skipBlock();
  518. }
  519. break;
  520. case 0x7A: // RAR New Subblock: RR, AV, CMT
  521. $block->rarReadPackedFileHeader();
  522. if ($block->fileName === 'RR') { // Recovery Record
  523. if (is_null($recovery)) {
  524. $recovery = array();
  525. $recovery['fileName'] = 'Protect+';
  526. $recovery['fileSize'] = 0;
  527. }
  528. $recovery['fileSize'] += $block->addSize;
  529. if (!$recovery_data_removed) {
  530. $block->skipBlock();
  531. }
  532. break;
  533. } // other types have no data removed and will be fully skipped
  534. $block->skipBlock();
  535. break;
  536. case 0x73: // RAR Volume Header
  537. // warnings for ASAP and IMMERSE -> crappy rars
  538. $ext = strtolower(substr($current_rar['fileName'], - 4));
  539. if (($block->flags & 0x0100) && $ext !== '.rar' && $ext !== '.001') {
  540. array_push($warnings, "MHD_FIRSTVOLUME flag set for {$current_rar['fileName']}.");
  541. }
  542. $is_new_style_naming = $block->flags & 0x0010 && $block->flags & 0x0001; // new numbering and a volume
  543. $current_rar['basenameVolume'] = getBasenameVolume($current_rar['fileName'], $is_new_style_naming);
  544. // encrypted block headers are used: these SRRs don't exist
  545. if ($block->flags & 0x0080) {
  546. $encrypted = TRUE;
  547. }
  548. case 0x72: // RAR Marker
  549. case 0x7B: // RAR Archive End
  550. case 0x75: // Old Comment
  551. case 0x76: // Old Authenticity
  552. case 0x77: // Old Subblock
  553. case 0x79: // Old Authenticity
  554. // no usefull stuff for us anymore: skip block and possible contents
  555. $block->skipBlock();
  556. break;
  557. default: // Unrecognized RAR/SRR block found!
  558. $block->skipBlock();
  559. if (!empty($current_rar['fileName'])) { // Psych.S06E02.HDTV.XviD-P0W4
  560. // -> P0W4 cleared RAR archive end block: almost all zeros except for the header length field
  561. array_push($warnings, "Unknown RAR block found in {$current_rar['fileName']}");
  562. } else { // e.g. a rar file that still has its contents
  563. array_push($warnings, 'ERROR: Not a SRR file?');
  564. return FALSE;
  565. //trigger_error('Not a SRR file.', E_USER_ERROR);
  566. }
  567. }
  568. // calculate size of the rar file + end offset
  569. if (!is_null($current_rar)) {
  570. if ($add_size === TRUE) {
  571. $current_rar['fileSize'] += $block->fullSize;
  572. }
  573. // store end offset of the header data of the rar volume
  574. $current_rar['offsetEnd'] = ftell($fh);
  575. // keep the results updated
  576. $rar_files[strtolower(basename($current_rar['fileName']))] = $current_rar;
  577. }
  578. // nuber of bytes we have processed
  579. $read = ftell($fh);
  580. // don't loop when bad data is encountered
  581. if ($read === $last_read) {
  582. break;
  583. }
  584. $last_read = $read;
  585. }
  586. // add sfv CRCs to all the rar files we have found
  587. foreach ($sfv['files'] as $key => $val) {
  588. // the capitalization between sfv and the actual file isn't always the same
  589. $lkey = strtolower($key);
  590. if (array_key_exists($lkey, $rar_files)) {
  591. $rar_files[$lkey]['fileCrc'] = strtoupper($val);
  592. // everything that stays can not be reconstructed (subs from .sfv files)
  593. unset($sfv['files'][$key]); // remove data from $sfv
  594. }
  595. }
  596. if ($customPacker) {
  597. array_push($warnings, 'Custom RAR packer detected.');
  598. }
  599. // return all info in a multi dimensional array
  600. return array(
  601. 'srrSize' => $srrSize,
  602. 'appName' => $appName,
  603. 'storedFiles' => $stored_files,
  604. 'rarFiles' => $rar_files,
  605. 'archivedFiles' => $archived_files,
  606. 'osoHashes' => $oso_hashes,
  607. // Recovery Records across all archives in the SRR data
  608. // the name is based on the first encountered recovery block
  609. // Protect! -> old style RAR recovery (before RAR 3.0)
  610. // Protect+ -> new style RAR recovery
  611. 'recovery' => $recovery,
  612. 'sfv' => $sfv, // comments and files that aren't covered by the SRR
  613. 'warnings' => $warnings, // when something unusual is found
  614. 'compressed' => $compressed,
  615. 'encrypted' => $encrypted
  616. );
  617. }
  618. /**
  619. * Same as the getStoredFileData() function, but based on the file name.
  620. * @param $srrfile The name of the SRR file to read.
  621. * @param $filename The file we want the contents from, including the path.
  622. * @return The bytes of the file or FALSE on failure.
  623. */
  624. function getStoredFile($srrfile, $filename) {
  625. $result = FALSE;
  626. $fh = fopen($srrfile, 'rb');
  627. if (flock($fh, LOCK_SH)) {
  628. $srr = processSrrHandle($fh);
  629. foreach($srr['storedFiles'] as $key => $value) {
  630. if($key === $filename) {
  631. $result = stream_get_contents($fh, $value['fileSize'], $value['fileOffset']);
  632. break;
  633. }
  634. }
  635. flock($fh, LOCK_UN); // release the lock
  636. }
  637. fclose($fh); // close the file
  638. return $result;
  639. }
  640. /**
  641. * Removes a file stored in the SRR file.
  642. * @param string $srrfile Path of the SRR file.
  643. * @param string $filename Path and name of the file to remove.
  644. * @return TRUE on success, FALSE otherwise
  645. */
  646. function removeFile($srrfile, $filename) {
  647. $result = FALSE;
  648. $fh = fopen($srrfile, 'c+b');
  649. if (flock($fh, LOCK_EX)) {
  650. $srr = processSrrHandle($fh);
  651. foreach ($srr['storedFiles'] as $key => $value) {
  652. if ($value['fileName'] === $filename) {
  653. // how much to remove? read the block starting from the offset
  654. fseek($fh, $value['blockOffset'], SEEK_SET);
  655. $warnings_stub = array();
  656. $block = new Block($fh, $warnings_stub);
  657. fseek($fh, $value['blockOffset'] + $block->fullSize, SEEK_SET);
  658. $after = fread($fh, $srr['srrSize']); // srrSize: the (max) amount to read
  659. ftruncate($fh, $value['blockOffset']);
  660. fseek($fh, 0, SEEK_END); // Upon success, returns 0; otherwise, returns -1.
  661. fwrite($fh, $after);
  662. $result = TRUE;
  663. break;
  664. }
  665. }
  666. flock($fh, LOCK_UN); // release the lock
  667. }
  668. fclose($fh); // close the file
  669. return $result;
  670. }
  671. /**
  672. * Adds a file to the saved files inside a SRR file.
  673. * @param string $srr The path of the SRR file.
  674. * @param string $file The file to store.
  675. * @param bytes $path The path that must be prefixed for the file name.
  676. * @return TRUE on success, FALSE otherwise.
  677. */
  678. function storeFileCli($srr, $file, $path='') {
  679. // the path must have the path separator included
  680. if ($path != '' && substr($path, -1) !== '/') {
  681. return FALSE;
  682. }
  683. $fileContents = file_get_contents($file);
  684. return storeFile($srr, $path . basename($file), $fileContents);
  685. }
  686. /**
  687. * Adds a file to the saved files inside a SRR file.
  688. * @param string $srrFile The path of the SRR file.
  689. * @param string $filePath The path and name that will be stored.
  690. * @param bytes $fdata The bytes of the file to store in the SRR file.
  691. * @return TRUE when storing succeeds.
  692. */
  693. function storeFile($srrFile, $filePath, $fdata) {
  694. // check for illegal windows characters
  695. // the path separator must be /
  696. // twice (//) may not be possible
  697. if (fileNameCheck($filePath)) {
  698. return FALSE;
  699. }
  700. $fh = fopen($srrFile, 'c+b');
  701. if (flock($fh, LOCK_EX)) {
  702. $srr = processSrrHandle($fh);
  703. // don't let the same file get added twice
  704. foreach($srr['storedFiles'] as $key => $value) {
  705. if($key === $filePath) {
  706. flock($fh, LOCK_UN);
  707. fclose($fh);
  708. return FALSE;
  709. }
  710. }
  711. $offset = newFileOffset($fh);
  712. if ($offset < 0) {
  713. // broken/empty .srr file due to bugs :(
  714. flock($fh, LOCK_UN);
  715. fclose($fh);
  716. return FALSE;
  717. }
  718. $after = fread($fh, $srr['srrSize']);
  719. $header = createStoredFileHeader($filePath, strlen($fdata));
  720. ftruncate($fh, $offset);
  721. fseek($fh, 0, SEEK_END); // Upon success, returns 0; otherwise, returns -1.
  722. fwrite($fh, $header);
  723. fwrite($fh, $fdata);
  724. fwrite($fh, $after);
  725. flock($fh, LOCK_UN); // release the lock
  726. }
  727. fclose($fh); // close the file
  728. return TRUE;
  729. }
  730. function addOsoHash($srrFile, $oso_hash_data) {
  731. $fh = fopen($srrFile, 'c+b');
  732. if (flock($fh, LOCK_EX)) {
  733. $result = processSrrHandle($fh);
  734. // the hash must not already exist
  735. foreach($result['osoHashes'] as $value) {
  736. if ($value['data'] == $oso_hash_data) {
  737. flock($fh, LOCK_UN);
  738. fclose($fh);
  739. return FALSE;
  740. }
  741. }
  742. fseek($fh, 0, SEEK_END); // is not necessary
  743. fwrite($fh, $oso_hash_data);
  744. flock($fh, LOCK_UN); // release the lock
  745. }
  746. fclose($fh); // close the file
  747. return TRUE;
  748. }
  749. // /**
  750. // * Adds a new OSO hash to the end of the SRR file.
  751. // *
  752. // * @param string $srr
  753. // * @param int $fileSize
  754. // * @param string $osoHash
  755. // * @param string $fileName
  756. // */
  757. // function addOsoHash($srr, $fileSize, $osoHash, $fileName) {
  758. // // check for illegal windows characters; no paths
  759. // if (fileNameCheck($fileName) || strstr($fileName, '/')) {
  760. // return FALSE;
  761. // }
  762. // if ($fileSize < 0 || !preg_match('/[a-f0-9]{16}/i', $osoHash) || strlen($fileName) < 1) {
  763. // return FALSE;
  764. // }
  765. // // the hash must not already exist
  766. // $result = processSrr($srr);
  767. // foreach($result['osoHashes'] as $value) {
  768. // if ($value['fileName'] == $fileName &&
  769. // $value['fileSize'] == $fileSize &&
  770. // $value['osoHash'] == $osoHash) {
  771. // return FALSE;
  772. // }
  773. // }
  774. // $fh = fopen($srr, 'rb');
  775. // $before = fread($fh, getFileSizeHandle($fh));
  776. // fclose($fh);
  777. // // 2 byte CRC, 1 byte block type, 2 bytes for the flag 0x0000
  778. // $header = pack('H*' , '6B6B6B0000');
  779. // $osoBlockHeader = encode_int($fileSize); // broken on 32 bit!!
  780. // // OSO hash stored as little endian
  781. // $reversed = '';
  782. // for($i=strlen($osoHash);$i>=0;$i-=2) {
  783. // $reversed .= substr($osoHash, $i, 2);
  784. // }
  785. // $osoBlockHeader .= pack('H*' , $reversed);
  786. // $osoBlockHeader .= pack('v', strlen($fileName));
  787. // $osoBlockHeader .= $fileName;
  788. // $headerSize = pack('v', 5 + 2 + 8 + 8 + 2 + strlen($fileName));
  789. // print_r(unpack('H*', $header . $headerSize . $osoBlockHeader));
  790. // //file_put_contents($srr, $before . $header . $headerSize . $osoBlockHeader, LOCK_EX);
  791. // return TRUE;
  792. // }
  793. // function encode_int($in, $pad_to_bits=64, $little_endian=true) {
  794. // $in = decbin($in);
  795. // $in = str_pad($in, $pad_to_bits, '0', STR_PAD_LEFT);
  796. // $out = '';
  797. // for ($i = 0, $len = strlen($in); $i < $len; $i += 8) {
  798. // $out .= chr(bindec(substr($in,$i,8)));
  799. // }
  800. // if($little_endian) $out = strrev($out);
  801. // return $out;
  802. // }
  803. /**
  804. * Renames a stored file.
  805. * @param string $srrFile The path of the SRR file.
  806. * @param string $oldName The path and file name of a stored file.
  807. * @param string $newName The new path and file name of a stored file.
  808. * @return TRUE on success, FALSE otherwise.
  809. */
  810. function renameFile($srrFile, $oldName, $newName) {
  811. if (fileNameCheck($newName)) {
  812. if ($CLI_APP) {
  813. print_r("The new file name is illegal. Use only forward slashes for paths.\n");
  814. }
  815. return FALSE;
  816. }
  817. $result = FALSE;
  818. $fh = fopen($srrFile, 'c+b');
  819. if (flock($fh, LOCK_EX)) {
  820. $srr = processSrrHandle($fh);
  821. // prevent renaming to a file that already exists
  822. foreach ($srr['storedFiles'] as $key => $value) {
  823. if ($key === $newName) {
  824. flock($fh, LOCK_UN);
  825. fclose($fh);
  826. return FALSE;
  827. }
  828. }
  829. // rename the first file
  830. foreach ($srr['storedFiles'] as $key => $value) {
  831. if ($value['fileName'] === $oldName) {
  832. fseek($fh, $value['blockOffset'], SEEK_SET);
  833. $warnings_stub = array();
  834. $block = new Block($fh, $warnings_stub);
  835. $block->srrReadStoredFileHeader();
  836. fseek($fh, $value['blockOffset'] + $block->hsize, SEEK_SET);
  837. $after = fread($fh, $srr['srrSize']); // srrSize: the (max) amount to read
  838. ftruncate($fh, $value['blockOffset']);
  839. fseek($fh, 0, SEEK_END); // Upon success, returns 0; otherwise, returns -1.
  840. $changedHeader = createStoredFileHeader($newName, $block->addSize);
  841. fwrite($fh, $changedHeader);
  842. fwrite($fh, $after);
  843. $result = TRUE;
  844. break;
  845. }
  846. }
  847. flock($fh, LOCK_UN); // release the lock
  848. }
  849. fclose($fh); // close the file
  850. return $result;
  851. }
  852. /**
  853. * Calculate hash to identify SRRs that cover the same RAR volumes.
  854. * The result can be wrong when the provided $rarFiles array is outdated.
  855. * @param string $srr The SRR file.
  856. * @param array $rarFiles The resulting array from processSrr().
  857. * @return Sha1 hash of the srr file
  858. */
  859. function calculateHash($srrfile, $rarFiles, $algorithm='sha1') {
  860. // do the calculation only on the sorted RAR volumes
  861. // this way it still yields the same result if the order of creation differs
  862. uasort($rarFiles, 'rarFileCmp'); // sort on filename without path, case insensitive
  863. // compared with pyReScene when capitals are used: same behavior
  864. // Parlamentet.S06E02.SWEDiSH-SQC
  865. $hashContext = hash_init($algorithm);
  866. $fh = fopen($srrfile, 'rb');
  867. if (flock($fh, LOCK_SH)) {
  868. // calculate hash only on the RAR metadata
  869. foreach ($rarFiles as $key => $value) {
  870. $start = $value['offsetStartRar'];
  871. $end = $value['offsetEnd'];
  872. $data = stream_get_contents($fh, ($end - $start), $start);
  873. hash_update($hashContext, $data);
  874. }
  875. flock($fh, LOCK_UN); // release the lock
  876. }
  877. fclose($fh); // close the file
  878. return hash_final($hashContext);
  879. }
  880. // Comparison function
  881. function rarFileCmp($a, $b) {
  882. if ($a['fileName'] == $b['fileName']) {
  883. return 0;
  884. }
  885. return (strtolower($a['fileName']) < strtolower($b['fileName'])) ? -1 : 1;
  886. }
  887. function calculateHashHandle($srrHandle, $rarFiles, $algorithm='sha1') {
  888. // do the calculation only on the sorted RAR volumes
  889. // this way it still yields the same result if the order of creation differs
  890. uasort($rarFiles, 'rarFileCmp'); // sort on filename without path, case insensitive
  891. $hashContext = hash_init($algorithm);
  892. // calculate hash only on the RAR metadata
  893. foreach ($rarFiles as $key => $value) {
  894. $start = $value['offsetStartRar'];
  895. $end = $value['offsetEnd'];
  896. $data = stream_get_contents($srrHandle, ($end - $start), $start);
  897. hash_update($hashContext, $data);
  898. }
  899. return hash_final($hashContext);
  900. }
  901. function calculateHashString($srrData, $rarFiles, $algorithm='sha1') {
  902. // do the calculation only on the sorted RAR volumes
  903. // this way it still yields the same result if the order of creation differs
  904. uasort($rarFiles, 'rarFileCmp'); // sort on filename without path, case insensitive
  905. $hashContext = hash_init($algorithm);
  906. // calculate hash only on the RAR metadata
  907. foreach ($rarFiles as $key => $value) {
  908. $start = $value['offsetStartRar'];
  909. $end = $value['offsetEnd'];
  910. $memoryLimit = 5 * 1024 * 1024;
  911. $fp = fopen("php://temp/maxmemory:$memoryLimit", 'r+');
  912. fputs($fp, $srrData);
  913. rewind($fp);
  914. $fileAttributes = fstat($fp);
  915. $data = stream_get_contents($fp, ($end - $start), $start);
  916. hash_update($hashContext, $data);
  917. }
  918. return hash_final($hashContext);
  919. }
  920. /**
  921. * Compare 2 SRR files and list the differences.
  922. * @param $one First SRR file path.
  923. * @param $two Second SRR file path.
  924. * @return Some complicated array with differences.
  925. */
  926. function compareSrr($one, $two) {
  927. $result = FALSE;
  928. $fho = fopen($one, 'rb');
  929. $fht = fopen($two, 'rb');
  930. if (flock($fho, LOCK_SH) && flock($fht, LOCK_SH)) {
  931. $rone = processSrrHandle($fho);
  932. $rtwo = processSrrHandle($fht);
  933. $result = compareSrrRaw($rone, $rtwo, $fho, $fht);
  934. flock($fho, LOCK_UN); // release the lock
  935. flock($fht, LOCK_UN);
  936. }
  937. fclose($fho); // close the files
  938. fclose($fht);
  939. return $result;
  940. }
  941. /**
  942. * Same as above, but the info arrays of the SRR files were read before.
  943. * 2 times less parsing of the SRR files.
  944. */
  945. function compareSrrRaw($rone, $rtwo, $fhone, $fhtwo) {
  946. $hashOne = calculateHashHandle($fhone, $rone['rarFiles']);
  947. $hashTwo = calculateHashHandle($fhtwo, $rtwo['rarFiles']);
  948. // ----- The RARs -----
  949. // rebuild data can be considered the same?
  950. $sameRarData = $hashOne === $hashTwo;
  951. // hash => file name
  952. $hashesOne = hashParts($fhone, $rone['rarFiles']);
  953. $hashesTwo = hashParts($fhtwo, $rtwo['rarFiles']);
  954. // hash => file name (of those names unique to the first array)
  955. $left = array_diff($hashesOne, $hashesTwo);
  956. $right = array_diff($hashesTwo, $hashesOne);
  957. if ($sameRarData && count(array_merge($left, $right)) === 0) {
  958. $sameRarNames = TRUE;
  959. } else {
  960. $sameRarNames = FALSE;
  961. // must be picked in the comparison as the other one doesn't have it
  962. $uniqueRarOne = array_values(array_diff_key($hashesOne, $hashesTwo));
  963. $uniqueRarTwo = array_values(array_diff_key($hashesTwo, $hashesOne));
  964. // of the ones that are the same, the best name should be picked by default
  965. $twiceHash = array_keys(array_intersect_key($left, $right));
  966. $namesRarOne = array();
  967. $namesRarTwo = array();
  968. foreach ($twiceHash as $value) {
  969. $l = $left[$value];
  970. $r = $right[$value];
  971. // heuristic: we want the one with the longest length
  972. // this one probably has a path added
  973. if (strlen($l) > strlen($r)) {
  974. array_push($namesRarOne, $l);
  975. } else {
  976. array_push($namesRarTwo, $r);
  977. }
  978. }
  979. }
  980. // ----- The stored files -----
  981. // we compare .nfo, .sfv, .srs and other files to check if they are the same
  982. // or not a notewhorthy difference (line endings, sfv comments, ...)
  983. // if they are the same, only the filename/path needs to be chosen
  984. $filesOne = $rone['storedFiles'];
  985. $filesTwo = $rtwo['storedFiles'];
  986. // same name, same data => OK
  987. // different name, same data => one of both probably has a bad name (paths should always be the same for nfos)
  988. $same = array(); // list of tuples (fileOne, fileTwo, best) (because they can have different names)
  989. $sameName = array(); // same name, different data => e.g. Mr.X and Mr.Y sitescripts banner added for NFOs
  990. // suggest the largest file?
  991. // different name, different data => nfos from fixes ect.
  992. $uniqueOne = $filesOne;
  993. $uniqueTwo = $filesTwo;
  994. // *** NFO ***
  995. $oneNfo = getFilesByExt($filesOne, '.nfo');
  996. $twoNfo = getFilesByExt($filesTwo, '.nfo');
  997. // do not process these files again
  998. // Returns an array containing all the values from array1 that are not present in any of the other arrays.
  999. $filesOne = array_diff_assoc($filesOne, $oneNfo);
  1000. $filesTwo = array_diff_assoc($filesTwo, $twoNfo);
  1001. $oneNfo = addNfoHash($oneNfo, $fhone);
  1002. $twoNfo = addNfoHash($twoNfo, $fhtwo);
  1003. foreach ($oneNfo as $okey => $ovalue) {
  1004. foreach ($twoNfo as $tkey => $tvalue) {
  1005. $toUnset = FALSE;
  1006. if ($ovalue['hash'] === $tvalue['hash']) {
  1007. array_push($same, array($okey, $tkey,
  1008. 'lines1' => $ovalue['lines'], 'lines2' => $tvalue['lines']));
  1009. $toUnset = TRUE;
  1010. } elseif ($ovalue['fileName'] === $tvalue['fileName']) {
  1011. // suggest the largest NFO file
  1012. if ($ovalue['fileSize'] > $tvalue['fileSize']) {
  1013. $best = 0;
  1014. } else {
  1015. $best = 1;
  1016. }
  1017. array_push($sameName, array($okey, $tkey, 'best' => $best,
  1018. 'lines1' => $ovalue['lines'], 'lines2' => $tvalue['lines']));
  1019. $toUnset = TRUE;
  1020. // TODO: show text diff?
  1021. }
  1022. if ($toUnset) {
  1023. // remove from the array
  1024. unset($uniqueOne[$okey]);
  1025. unset($uniqueTwo[$tkey]);
  1026. }
  1027. }
  1028. }
  1029. // *** SFV ***
  1030. $oneSfv = getFilesByExt($filesOne, '.sfv');
  1031. $twoSfv = getFilesByExt($filesTwo, '.sfv');
  1032. // do not process these files again
  1033. $filesOne = array_diff_assoc($filesOne, $oneSfv);
  1034. $filesTwo = array_diff_assoc($filesTwo, $twoSfv);
  1035. $oneSfv = addSfvInfo($oneSfv, $fhone);
  1036. $twoSfv = addSfvInfo($twoSfv, $fhtwo);
  1037. foreach ($oneSfv as $okey => $ovalue) {
  1038. foreach ($twoSfv as $tkey => $tvalue) {
  1039. $toUnset = FALSE;
  1040. if ($ovalue['files'] === $tvalue['files']) {
  1041. // suggest the SFV file with the most comments
  1042. if (count($ovalue['comments']) > count($tvalue['comments'])) {
  1043. $best = 0;
  1044. } elseif (count($ovalue['comments']) < count($tvalue['comments'])) {
  1045. $best = 1;
  1046. } else {
  1047. // SFV with the longest file name has probably path info
  1048. if (strlen($ovalue['fileName']) > strlen($tvalue['fileName'])) {
  1049. $best = 0;
  1050. } else {
  1051. $best = 1;
  1052. }
  1053. }
  1054. array_push($same, array($okey, $tkey, 'best' => $best));
  1055. $toUnset = TRUE;
  1056. } elseif ($ovalue['fileName'] === $tvalue['fileName']) {
  1057. array_push($sameName, array($okey, $tkey));
  1058. $toUnset = TRUE;
  1059. }
  1060. if ($toUnset) {
  1061. unset($uniqueOne[$okey]);
  1062. unset($uniqueTwo[$tkey]);
  1063. }
  1064. }
  1065. }
  1066. // *** SRS ***
  1067. $oneSrs = getFilesByExt($filesOne, '.srs');
  1068. $twoSrs = getFilesByExt($filesTwo, '.srs');
  1069. // do not process these files again
  1070. $filesOne = array_diff_assoc($filesOne, $oneSrs);
  1071. $filesTwo = array_diff_assoc($filesTwo, $twoSrs);
  1072. $oneSrs = addSrsInfo($oneSrs, $fhone);
  1073. $twoSrs = addSrsInfo($twoSrs, $fhtwo);
  1074. //print_r($oneSrs);
  1075. //print_r($twoSrs);
  1076. foreach ($oneSrs as $okey => $ovalue) {
  1077. foreach ($twoSrs as $tkey => $tvalue) {
  1078. $toUnset = FALSE;
  1079. // sample name and crc32 must be the same to be the same sample
  1080. if ($ovalue['fileData']->name === $tvalue['fileData']->name &&
  1081. $ovalue['fileData']->crc32 === $tvalue['fileData']->crc32) {
  1082. // checked against main movie file
  1083. if ($ovalue['trackData'][1]->matchOffset === $tvalue['trackData'][1]->matchOffset) {
  1084. // equal enough
  1085. array_push($same, array($okey, $tkey));
  1086. $toUnset = TRUE;
  1087. } else {
  1088. // -c parameter difference
  1089. // indicate which one had the -c parameter used
  1090. if ($ovalue['trackData'][1]->matchOffset != 0) {
  1091. $best = 0;
  1092. } elseif ($tvalue['trackData'][1]->matchOffset != 0) {
  1093. $best = 1;
  1094. } else {
  1095. // suggest longest file name
  1096. if (strlen($ovalue['fileName']) > strlen($tvalue['fileName'])) {
  1097. $best = 0;
  1098. } else {
  1099. $best = 1;
  1100. }
  1101. }
  1102. array_push($sameName, array($okey, $tkey, 'best' => $best));
  1103. $toUnset = TRUE;
  1104. }
  1105. }
  1106. if ($toUnset) {
  1107. unset($uniqueOne[$okey]);
  1108. unset($uniqueTwo[$tkey]);
  1109. }
  1110. }
  1111. }
  1112. // *** OTHER ***
  1113. foreach ($filesOne as $okey => $ovalue) {
  1114. foreach ($filesTwo as $tkey => $tvalue) {
  1115. $toUnset = FALSE;
  1116. // same CRC: exactly the same
  1117. if ($ovalue['fileCrc'] === $tvalue['fileCrc']) {
  1118. array_push($same, array($okey, $tkey));
  1119. $toUnset = TRUE;
  1120. // they only have the same name
  1121. } elseif ($ovalue['fileName'] === $tvalue['fileName']) {
  1122. array_push($sameName, array($okey, $tkey));
  1123. $toUnset = TRUE;
  1124. }
  1125. if ($toUnset) {
  1126. unset($uniqueOne[$okey]);
  1127. unset($uniqueTwo[$tkey]);
  1128. }
  1129. }
  1130. }
  1131. $result = array(
  1132. 'sameRarData' => $sameRarData,
  1133. 'sameRarNames' => $sameRarNames,
  1134. 'same' => $same,
  1135. 'sameName' => $sameName,
  1136. 'uniqueOne' => array_keys($uniqueOne),
  1137. 'uniqueTwo' => array_keys($uniqueTwo),
  1138. );
  1139. if (!$sameRarNames) {
  1140. // these 4 lists cover all unique RAR metadata
  1141. $result = array_merge($result, array(
  1142. 'uniqueRarOne' => $uniqueRarOne, // RAR files that are new
  1143. 'uniqueRarTwo' => $uniqueRarTwo,
  1144. 'namesRarOne' => $namesRarOne, // the RAR names that are better (when content is the same)
  1145. 'namesRarTwo' => $namesRarTwo, // these should be picked by default when mergeing
  1146. ));
  1147. }
  1148. return $result;
  1149. }
  1150. function getFilesByExt($fileList, $extension) {
  1151. $result = array();
  1152. foreach ($fileList as $key => $value) {
  1153. if (strtolower(substr($value['fileName'], - 4)) === $extension) {
  1154. $result[$key] = $value;
  1155. }
  1156. }
  1157. return $result;
  1158. }
  1159. function addNfoHash($list, $fileHandle) {
  1160. foreach($list as $key => $value) {
  1161. // store nfo hash next to the other stored file data
  1162. $nfoData = stream_get_contents($fileHandle, $value['fileSize'], $value['fileOffset']);
  1163. $list[$key]['hash'] = nfoHash($nfoData);
  1164. // check for which nfo has the fewest lines -> probably no unnessesary white lines
  1165. // Indiana.Jones.And.The.Last.Crusade.1989.PAL.DVDR-DNA
  1166. $list[$key]['lines'] = count(explode("\n", $nfoData));
  1167. }
  1168. return $list;
  1169. }
  1170. function addSfvInfo($list, $fileHandle) {
  1171. foreach($list as $key => $value) {
  1172. $result = processSfv(stream_get_contents($fileHandle, $value['fileSize'], $value['fileOffset']));
  1173. $list[$key]['comments'] = $result['comments'];
  1174. $list[$key]['files'] = $result['files'];
  1175. }
  1176. return $list;
  1177. }
  1178. function addSrsInfo($list, $fileHandle) {
  1179. foreach($list as $key => $value) {
  1180. $result = processSrsData(stream_get_contents($fileHandle, $value['fileSize'], $value['fileOffset']));
  1181. //print_r($result);
  1182. $list[$key] += $result;
  1183. }
  1184. return $list;
  1185. }
  1186. function nfoHash($nfoData) {
  1187. // ignore all new lines
  1188. $string = preg_replace("/(\r\n|\r|\n)/", '', $nfoData);
  1189. // trailing whitespace can be stripped sometimes too
  1190. $string = rtrim($string);
  1191. return md5($string);
  1192. }
  1193. /**
  1194. * Merge two SRR files by selecting the wanted data parts from each of them.
  1195. * @param $one First SRR file.
  1196. * @param $two Second SRR file.
  1197. */
  1198. function mergeSrr($one, $two, $storeOne, $storeTwo, $rarOne, $rarTwo, $result) {
  1199. $rone = processSrr($one);
  1200. $rtwo = processSrr($two);
  1201. }
  1202. function processSrsData(&$srsFileData) {
  1203. // http://www.php.net/manual/en/wrappers.php.php
  1204. // Set the limit to 5 MiB. After this limit, a temporary file will be used.
  1205. $memoryLimit = 5 * 1024 * 1024;
  1206. $fp = fopen("php://temp/maxmemory:$memoryLimit", 'r+');
  1207. fputs($fp, $srsFileData);
  1208. rewind($fp);
  1209. $fileAttributes = fstat($fp);
  1210. return processSrsHandle($fp, $fileAttributes['size']);
  1211. }
  1212. /**
  1213. * Parses an SRS file.
  1214. * @param $fileHandle
  1215. * @param int $srsSize
  1216. * @return info array
  1217. */
  1218. function processSrsHandle($fileHandle, $srsSize) {
  1219. $result = null;
  1220. switch(detectFileFormat($fileHandle)) {
  1221. case FileType::AVI:
  1222. $result = parse_srs_avi($fileHandle, $srsSize);
  1223. break;
  1224. case FileType::MKV:
  1225. $result = parse_srs_mkv($fileHandle, $srsSize);
  1226. break;
  1227. case FileType::MP4:
  1228. $result = parse_srs_mp4($fileHandle, $srsSize);
  1229. break;
  1230. case FileType::WMV:
  1231. $result = parse_srs_wmv($fileHandle, $srsSize);
  1232. break;
  1233. case FileType::FLAC:
  1234. $result = parse_srs_flac($fileHandle, $srsSize);
  1235. break;
  1236. case FileType::MP3:
  1237. $result = parse_srs_mp3($fileHandle, $srsSize);
  1238. break;
  1239. case FileType::STREAM:
  1240. $result = parse_srs_stream($fileHandle, $srsSize);
  1241. break;
  1242. default:
  1243. global $CLI_APP;
  1244. if ($CLI_APP) { // don't show the message when used as library
  1245. echo 'SRS file type not detected';
  1246. }
  1247. break;
  1248. }
  1249. fclose($fileHandle);
  1250. return $result;
  1251. }
  1252. /**
  1253. * Sorts the stored files in $srrFile according to $sortedFileNameList.
  1254. * @param string $srrFile: path to the SRR file
  1255. * @param array $sortedFileNameList: simple array with file names
  1256. * @return bool success status
  1257. */
  1258. function sortStoredFiles($srrFile, $sortedFileNameList) {
  1259. $fh = fopen($srrFile, 'c+b');
  1260. if (flock($fh, LOCK_EX)) {
  1261. $srrInfo = processSrrHandle($fh);
  1262. // not the same amount of elements: bad input
  1263. if (count($srrInfo['storedFiles']) != count($sortedFileNameList) ||
  1264. count(preg_grep("/^Duplicate/", $srrInfo['warnings'])) > 0) {
  1265. flock($fh, LOCK_UN);
  1266. fclose($fh);
  1267. return FALSE;
  1268. }
  1269. $before = $srrInfo['srrSize'];
  1270. $after = 0;
  1271. // check if each name is the same in both lists
  1272. foreach ($srrInfo['storedFiles'] as $key => $value) {
  1273. if (array_search($key, $sortedFileNameList) === FALSE) {
  1274. flock($fh, LOCK_UN);
  1275. fclose($fh);
  1276. return FALSE;
  1277. }
  1278. // offsets where the stored files start and end
  1279. if ($value['blockOffset'] < $before) {
  1280. $before = $value['blockOffset'];
  1281. }
  1282. $offset = $value['fileOffset'] + $value['fileSize'];
  1283. if ($offset > $after) {
  1284. $after = $offset;
  1285. }
  1286. }
  1287. fseek($fh, 0, SEEK_SET);
  1288. $beforeData = fread($fh, $before);
  1289. fseek($fh, $after, SEEK_SET);
  1290. $afterData = fread($fh, $srrInfo['srrSize']); // srrSize: the (max) amount to read
  1291. // sort the files and grab their blocks
  1292. $between = '';
  1293. foreach ($sortedFileNameList as $key) {
  1294. $bo = $srrInfo['storedFiles'][$key]['blockOffset'];
  1295. $fo = $srrInfo['storedFiles'][$key]['fileOffset'];
  1296. $fs = $srrInfo['storedFiles'][$key]['fileSize'];
  1297. $blockSize = ($fo + $fs) - $bo;
  1298. fseek($fh, $bo, SEEK_SET);
  1299. $between .= fread($fh, $blockSize);
  1300. }
  1301. $bytesWritten = 0;
  1302. fseek($fh, 0, SEEK_SET);
  1303. $bytesWritten += fwrite($fh, $beforeData);
  1304. $bytesWritten += fwrite($fh, $between);
  1305. $bytesWritten += fwrite($fh, $afterData);
  1306. flock($fh, LOCK_UN); // release the lock
  1307. fclose($fh); // close the file
  1308. return assert($bytesWritten === $srrInfo['srrSize']);
  1309. }
  1310. fclose($fh); // close the file
  1311. return TRUE;
  1312. }
  1313. /**
  1314. * Returns the data of an SRR file that only contains the SFV and
  1315. * the RAR meta data of a certain RAR set. Capitals are ignored for $volume.
  1316. * @param file $srrFile
  1317. * @param string $volume
  1318. * @param string $applicationName
  1319. * @return string
  1320. */
  1321. function grabSrrSubset($srrFile, $volume, $applicationName = 'rescene.php partial SRR file') {
  1322. $result = '';
  1323. $fh = fopen($srrFile, 'rb');
  1324. if (flock($fh, LOCK_SH)) {
  1325. $srrInfo = processSrrHandle($fh);
  1326. $volume = strtolower($volume);
  1327. // 1) construct SRR file header
  1328. $result = createSrrHeaderBlock($applicationName);
  1329. // 2) add the right SFV file
  1330. foreach ($srrInfo['storedFiles'] as $key => $value) {
  1331. if (strtolower(substr($key, -4)) === '.sfv' &&
  1332. strtolower($value['basenameVolume']) === $volume) {
  1333. $length = $value['fileSize'] + ($value['fileOffset'] - $value['blockOffset']);
  1334. $result .= stream_get_contents($fh, $length, $value['blockOffset']);
  1335. }
  1336. }
  1337. // 3) add the right RAR meta data
  1338. foreach ($srrInfo['rarFiles'] as $key => $value) {
  1339. if (strtolower($value['basenameVolume']) === $volume) {
  1340. $length = $value['offsetEnd'] - $value['offsetStartSrr'];
  1341. $result .= stream_get_contents($fh, $length, $value['offsetStartSrr']);
  1342. }
  1343. }
  1344. // 4) ignore everything else
  1345. flock($fh, LOCK_UN); // release the lock
  1346. }
  1347. fclose($fh); // close the file
  1348. return $result;
  1349. }
  1350. /**
  1351. * Retrieve and parse the vobsubs languages
  1352. * @param string $srrFile
  1353. * @param array $srrInfo
  1354. * @return array with languages structure
  1355. */
  1356. function getVobsubLanguages($srrFile, $srrInfo = null) {
  1357. $languages = array();
  1358. $fh = fopen($srrFile, 'rb');
  1359. if (flock($fh, LOCK_SH)) {
  1360. // avoid parsing SRR again when parameter is provided
  1361. if ($srrInfo === null) {
  1362. $srrInfo = processSrrHandle($fh);
  1363. }
  1364. // get a list of all stored .srr files with languages.diz in them
  1365. $dizFiles = array();
  1366. foreach ($srrInfo['storedFiles'] as $key => $value) {
  1367. if (substr($key, -4) === '.srr') {
  1368. $storedSrr = stream_get_contents($fh, $value['fileSize'], $value['fileOffset']);
  1369. if (strpos($storedSrr, 'languages.diz') !== FALSE) {
  1370. $vobsubSrr = processSrrData($storedSrr);
  1371. $lv = $vobsubSrr['storedFiles']['languages.diz']; // can fail in theory
  1372. $dizFiles[$key] = substr($storedSrr, $lv['fileOffset'], $lv['fileSize']);
  1373. }
  1374. }
  1375. }
  1376. flock($fh, LOCK_UN); // release the lock
  1377. foreach ($dizFiles as $srrVobsubName => $dizData) {
  1378. $languages[$srrVobsubName] = parseLanguagesDiz($dizData);
  1379. }
  1380. }
  1381. fclose($fh); // close the file
  1382. return $languages;
  1383. }
  1384. // Private helper functions -------------------------------------------------------------------------------------------
  1385. function parseLanguagesDiz($data) {
  1386. $idx = array();
  1387. $lastFileName = 'NO IDX FILE NAME DETECTED';
  1388. $lines = preg_split('/$\R?^/m', $data); // early .srr files are not just \n
  1389. foreach ($lines as $line) {
  1390. if (substr($line, 0, 1) === '#') {
  1391. // new idx file
  1392. $lastFileName = substr($line, 2);
  1393. $idx[$lastFileName] = array();
  1394. } else {
  1395. // new language line
  1396. preg_match('/id: ([a-z]{2}).*?,.*/i', $line, $matches);
  1397. if ($matches) { // '--' gets skipped e.g. Zero.Days.2016.PROPER.DVDRip.x264-WiDE
  1398. array_push($idx[$lastFileName], $matches[1]);
  1399. }
  1400. }
  1401. }
  1402. return $idx;
  1403. }
  1404. /**
  1405. * No locking occurs.
  1406. * @param unknown_type $fileHandle
  1407. * @return integer The size of the file.
  1408. */
  1409. function getFileSizeHandle($fileHandle) {
  1410. // PHP uses caching for filesize() and we do not always want that!
  1411. $stat

Large files files are truncated, but you can click here to view the full file