Completed
Push — dev ( 51dd92...483855 )
by Darko
08:06
created

ProcessAdditional::_processNZBCompressedFiles()   D

Complexity

Conditions 19
Paths 65

Size

Total Lines 84
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 380

Importance

Changes 0
Metric Value
eloc 46
dl 0
loc 84
ccs 0
cts 39
cp 0
rs 4.5166
c 0
b 0
f 0
cc 19
nc 65
nop 1
crap 380

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Blacklight\processing\post;
4
5
use FFMpeg\FFMpeg;
6
use Blacklight\Nfo;
7
use Blacklight\NZB;
8
use FFMpeg\FFProbe;
9
use Blacklight\NNTP;
10
use App\Models\Group;
11
use App\Models\Release;
12
use App\Models\Category;
13
use App\Models\Settings;
14
use Blacklight\ColorCLI;
15
use Blacklight\Releases;
16
use Blacklight\NameFixer;
17
use Blacklight\Categorize;
18
use App\Models\ReleaseFile;
19
use Blacklight\ReleaseExtra;
20
use Blacklight\ReleaseImage;
21
use Blacklight\SphinxSearch;
22
use FFMpeg\Format\Video\Ogg;
23
use Mhor\MediaInfo\MediaInfo;
24
use Blacklight\utility\Utility;
25
use dariusiii\rarinfo\Par2Info;
26
use FFMpeg\Coordinate\TimeCode;
27
use FFMpeg\Format\Audio\Vorbis;
28
use FFMpeg\Coordinate\Dimension;
29
use dariusiii\rarinfo\ArchiveInfo;
30
use Illuminate\Support\Facades\DB;
31
use Illuminate\Support\Facades\Log;
32
use Illuminate\Support\Facades\File;
33
use FFMpeg\Filters\Video\ResizeFilter;
34
35
class ProcessAdditional
36
{
37
    /**
38
     * How many compressed (rar/zip) files to check.
39
     *
40
     * @var int
41
     */
42
    public const maxCompressedFilesToCheck = 20;
43
44
    /**
45
     * @var \PDO
46
     */
47
    public $pdo;
48
49
    /**
50
     * @var bool
51
     */
52
    protected $_echoDebug;
53
54
    /**
55
     * @var
56
     */
57
    protected $_releases;
58
59
    /**
60
     * Count of releases to work on.
61
     * @var int
62
     */
63
    protected $_totalReleases;
64
65
    /**
66
     * @var
67
     */
68
    protected $_release;
69
70
    /**
71
     * @var \Blacklight\NZB
72
     */
73
    protected $_nzb;
74
75
    /**
76
     * List of files with sizes/etc contained in the NZB.
77
     * @var array
78
     */
79
    protected $_nzbContents;
80
81
    /**
82
     * @var \dariusiii\rarinfo\Par2Info
83
     */
84
    protected $_par2Info;
85
86
    /**
87
     * @var \dariusiii\rarinfo\ArchiveInfo
88
     */
89
    protected $_archiveInfo;
90
91
    /**
92
     * @var bool|null|string
93
     */
94
    protected $_innerFileBlacklist;
95
96
    /**
97
     * @var int
98
     */
99
    protected $_maxNestedLevels;
100
101
    /**
102
     * @var null|string
103
     */
104
    protected $_7zipPath;
105
106
    /**
107
     * @var null|string
108
     */
109
    protected $_unrarPath;
110
111
    /**
112
     * @var string
113
     */
114
    protected $_killString;
115
116
    /**
117
     * @var bool|string
118
     */
119
    protected $_showCLIReleaseID;
120
121
    /**
122
     * @var int
123
     */
124
    protected $_queryLimit;
125
126
    /**
127
     * @var int
128
     */
129
    protected $_segmentsToDownload;
130
131
    /**
132
     * @var int
133
     */
134
    protected $_maximumRarSegments;
135
136
    /**
137
     * @var int
138
     */
139
    protected $_maximumRarPasswordChecks;
140
141
    /**
142
     * @var string
143
     */
144
    protected $_maxSize;
145
146
    /**
147
     * @var string
148
     */
149
    protected $_minSize;
150
151
    /**
152
     * @var bool
153
     */
154
    protected $_processThumbnails;
155
156
    /**
157
     * @var string
158
     */
159
    protected $_audioSavePath;
160
161
    /**
162
     * @var string
163
     */
164
    protected $_supportFileRegex;
165
166
    /**
167
     * @var bool
168
     */
169
    protected $_echoCLI;
170
171
    /**
172
     * @var \Blacklight\NNTP
173
     */
174
    protected $_nntp;
175
176
    /**
177
     * @var \Blacklight\Categorize
178
     */
179
    protected $_categorize;
180
181
    /**
182
     * @var \Blacklight\NameFixer
183
     */
184
    protected $_nameFixer;
185
186
    /**
187
     * @var \Blacklight\ReleaseExtra
188
     */
189
    protected $_releaseExtra;
190
191
    /**
192
     * @var \Blacklight\ReleaseImage
193
     */
194
    protected $_releaseImage;
195
196
    /**
197
     * @var \Blacklight\Nfo
198
     */
199
    protected $_nfo;
200
201
    /**
202
     * @var bool
203
     */
204
    protected $_extractUsingRarInfo;
205
206
    /**
207
     * @var bool
208
     */
209
    protected $_alternateNNTP;
210
211
    /**
212
     * @var int
213
     */
214
    protected $_ffMPEGDuration;
215
216
    /**
217
     * @var bool
218
     */
219
    protected $_addPAR2Files;
220
221
    /**
222
     * @var bool
223
     */
224
    protected $_processVideo;
225
226
    /**
227
     * @var bool
228
     */
229
    protected $_processJPGSample;
230
231
    /**
232
     * @var bool
233
     */
234
    protected $_processAudioSample;
235
236
    /**
237
     * @var bool
238
     */
239
    protected $_processMediaInfo;
240
241
    /**
242
     * @var bool
243
     */
244
    protected $_processAudioInfo;
245
246
    /**
247
     * @var bool
248
     */
249
    protected $_processPasswords;
250
251
    /**
252
     * @var string
253
     */
254
    protected $_audioFileRegex;
255
256
    /**
257
     * @var string
258
     */
259
    protected $_ignoreBookRegex;
260
261
    /**
262
     * @var string
263
     */
264
    protected $_videoFileRegex;
265
266
    /**
267
     * Have we created a video file for the current release?
268
     * @var bool
269
     */
270
    protected $_foundVideo;
271
272
    /**
273
     * Have we found MediaInfo data for a Video for the current release?
274
     * @var bool
275
     */
276
    protected $_foundMediaInfo;
277
278
    /**
279
     * Have we found MediaInfo data for a Audio file for the current release?
280
     * @var bool
281
     */
282
    protected $_foundAudioInfo;
283
284
    /**
285
     * Have we created a short Audio file sample for the current release?
286
     * @var bool
287
     */
288
    protected $_foundAudioSample;
289
290
    /**
291
     * Extension of the found audio file (MP3/FLAC/etc).
292
     * @var string
293
     */
294
    protected $_AudioInfoExtension;
295
296
    /**
297
     * Have we downloaded a JPG file for the current release?
298
     * @var bool
299
     */
300
    protected $_foundJPGSample;
301
302
    /**
303
     * Have we created a Video JPG image sample for the current release?
304
     * @var bool
305
     */
306
    protected $_foundSample;
307
308
    /**
309
     * Have we found PAR2 info on this release?
310
     * @var bool
311
     */
312
    protected $_foundPAR2Info;
313
314
    /**
315
     * Message ID's for found content to download.
316
     * @var array
317
     */
318
    protected $_sampleMessageIDs;
319
    protected $_JPGMessageIDs;
320
    protected $_MediaInfoMessageIDs;
321
    protected $_AudioInfoMessageIDs;
322
    protected $_RARFileMessageIDs;
323
324
    /**
325
     * Password status of the current release.
326
     * @var array
327
     */
328
    protected $_passwordStatus;
329
330
    /**
331
     * Does the current release have a password?
332
     * @var bool
333
     */
334
    protected $_releaseHasPassword;
335
336
    /**
337
     * Does the current release have an NFO file?
338
     * @var bool
339
     */
340
    protected $_releaseHasNoNFO;
341
342
    /**
343
     * Name of the current release's usenet group.
344
     * @var string
345
     */
346
    protected $_releaseGroupName;
347
348
    /**
349
     * Number of file information added to DB (from rar/zip/par2 contents).
350
     * @var int
351
     */
352
    protected $_addedFileInfo;
353
354
    /**
355
     * Number of file information we found from RAR/ZIP.
356
     * (if some of it was already in DB, this count goes up, while the count above does not).
357
     * @var int
358
     */
359
    protected $_totalFileInfo;
360
361
    /**
362
     * How many compressed (rar/zip) files have we checked.
363
     * @var int
364
     */
365
    protected $_compressedFilesChecked;
366
367
    /**
368
     * Should we download the last rar?
369
     * @var bool
370
     */
371
    protected $_fetchLastFiles;
372
373
    /**
374
     * Are we downloading the last rar?
375
     * @var bool
376
     */
377
    protected $_reverse;
378
379
    /**
380
     * @var \Blacklight\SphinxSearch
381
     */
382
    protected $sphinx;
383
384
    /**
385
     * @var \FFMpeg\FFMpeg
386
     */
387
    private $ffmpeg;
388
389
    /**
390
     * @var \FFMpeg\FFProbe
391
     */
392
    private $ffprobe;
393
394
    /**
395
     * @var \Mhor\MediaInfo\MediaInfo
396
     */
397
    private $mediaInfo;
398
399
    /**
400
     * ProcessAdditional constructor.
401
     *
402
     * @param array $options
403
     * @throws \Exception
404
     */
405
    public function __construct(array $options = [])
406
    {
407
        $defaults = [
408
            'Echo'         => false,
409
            'Categorize'   => null,
410
            'Groups'       => null,
411
            'NameFixer'    => null,
412
            'Nfo'          => null,
413
            'NNTP'         => null,
414
            'NZB'          => null,
415
            'ReleaseExtra' => null,
416
            'ReleaseImage' => null,
417
            'Settings'     => null,
418
            'SphinxSearch' => null,
419
        ];
420
        $options += $defaults;
421
422
        $this->_echoCLI = ($options['Echo'] && config('nntmux.echocli') && (strtolower(PHP_SAPI) === 'cli'));
423
424
        $this->_nntp = $options['NNTP'] instanceof NNTP ? $options['NNTP'] : new NNTP(['Echo' => $this->_echoCLI]);
425
426
        $this->_nzb = $options['NZB'] instanceof NZB ? $options['NZB'] : new NZB();
427
        $this->_archiveInfo = new ArchiveInfo();
428
        $this->_categorize = $options['Categorize'] instanceof Categorize ? $options['Categorize'] : new Categorize();
429
        $this->_nameFixer = $options['NameFixer'] instanceof NameFixer ? $options['NameFixer'] : new NameFixer(['Echo' =>$this->_echoCLI, 'Groups' => null, 'Categorize' => $this->_categorize]);
430
        $this->_releaseExtra = $options['ReleaseExtra'] instanceof ReleaseExtra ? $options['ReleaseExtra'] : new ReleaseExtra();
431
        $this->_releaseImage = $options['ReleaseImage'] instanceof ReleaseImage ? $options['ReleaseImage'] : new ReleaseImage();
432
        $this->_par2Info = new Par2Info();
433
        $this->_nfo = $options['Nfo'] instanceof Nfo ? $options['Nfo'] : new Nfo();
434
        $this->sphinx = $options['SphinxSearch'] instanceof SphinxSearch ? $options['SphinxSearch'] : new SphinxSearch();
435
        $this->ffmpeg = FFMpeg::create(['timeout' => Settings::settingValue('..timeoutseconds')]);
436
        $this->ffprobe = FFProbe::create();
437
        $this->mediaInfo = new MediaInfo();
438
        $this->mediaInfo->setConfig('use_oldxml_mediainfo_output_format', true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $value of Mhor\MediaInfo\MediaInfo::setConfig(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

438
        $this->mediaInfo->setConfig('use_oldxml_mediainfo_output_format', /** @scrutinizer ignore-type */ true);
Loading history...
439
        $this->mediaInfo->setConfig('command', Settings::settingValue('apps..mediainfopath'));
440
441
        $this->_innerFileBlacklist = Settings::settingValue('indexer.ppa.innerfileblacklist') === '' ? false : Settings::settingValue('indexer.ppa.innerfileblacklist');
0 ignored issues
show
introduced by
The condition App\Models\Settings::set...rfileblacklist') === '' is always false.
Loading history...
442
        $this->_maxNestedLevels = (int) Settings::settingValue('..maxnestedlevels') === 0 ? 3 : (int) Settings::settingValue('..maxnestedlevels');
443
        $this->_extractUsingRarInfo = (int) Settings::settingValue('..extractusingrarinfo') !== 0;
444
        $this->_fetchLastFiles = (int) Settings::settingValue('archive.fetch.end') !== 0;
445
446
        $this->_7zipPath = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type null|string of property $_7zipPath.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
447
        $this->_unrarPath = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type null|string of property $_unrarPath.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
448
449
        // Pass the binary extractors to ArchiveInfo.
450
        $clients = [];
451
        if (Settings::settingValue('apps..unrarpath') !== '') {
0 ignored issues
show
introduced by
The condition App\Models\Settings::set...pps..unrarpath') !== '' is always true.
Loading history...
452
            $clients += [ArchiveInfo::TYPE_RAR => Settings::settingValue('apps..unrarpath')];
453
            $this->_unrarPath = Settings::settingValue('apps..unrarpath');
454
        }
455
        if (Settings::settingValue('apps..zippath') !== '') {
0 ignored issues
show
introduced by
The condition App\Models\Settings::set...'apps..zippath') !== '' is always true.
Loading history...
456
            $clients += [ArchiveInfo::TYPE_ZIP => Settings::settingValue('apps..zippath')];
457
            $this->_7zipPath = Settings::settingValue('apps..zippath');
458
        }
459
        $this->_archiveInfo->setExternalClients($clients);
460
461
        $this->_killString = '"';
462
        if (Settings::settingValue('apps..timeoutpath') !== '' && (int) Settings::settingValue('..timeoutseconds') > 0) {
463
            $this->_killString = (
464
                '"'.Settings::settingValue('apps..timeoutpath').
465
                '" --foreground --signal=KILL '.
466
                Settings::settingValue('..timeoutseconds').' "'
467
            );
468
        }
469
470
        $this->_showCLIReleaseID = (PHP_BINARY.' '.__DIR__.DS.'ProcessAdditional.php ReleaseID: ');
471
472
        // Maximum amount of releases to fetch per run.
473
        $this->_queryLimit =
474
            (Settings::settingValue('..maxaddprocessed') !== '') ? (int) Settings::settingValue('..maxaddprocessed') : 25;
0 ignored issues
show
introduced by
The condition App\Models\Settings::set...axaddprocessed') !== '' is always true.
Loading history...
475
476
        // Maximum message ID's to download per file type in the NZB (video, jpg, etc).
477
        $this->_segmentsToDownload =
478
            (Settings::settingValue('..segmentstodownload') !== '') ? (int) Settings::settingValue('..segmentstodownload') : 2;
0 ignored issues
show
introduced by
The condition App\Models\Settings::set...entstodownload') !== '' is always true.
Loading history...
479
480
        // Maximum message ID's to download for a RAR file.
481
        $this->_maximumRarSegments =
482
            (Settings::settingValue('..maxpartsprocessed') !== '') ? (int) Settings::settingValue('..maxpartsprocessed') : 3;
0 ignored issues
show
introduced by
The condition App\Models\Settings::set...partsprocessed') !== '' is always true.
Loading history...
483
484
        // Maximum RAR files to check for a password before stopping.
485
        $this->_maximumRarPasswordChecks =
486
            (Settings::settingValue('..passchkattempts') !== '') ? (int) Settings::settingValue('..passchkattempts') : 1;
0 ignored issues
show
introduced by
The condition App\Models\Settings::set...asschkattempts') !== '' is always true.
Loading history...
487
488
        $this->_maximumRarPasswordChecks = ($this->_maximumRarPasswordChecks < 1 ? 1 : $this->_maximumRarPasswordChecks);
489
490
        // Maximum size of releases in GB.
491
        $this->_maxSize =
492
            (Settings::settingValue('..maxsizetopostprocess') !== '') ? (int) Settings::settingValue('..maxsizetopostprocess') : 100;
0 ignored issues
show
introduced by
The condition App\Models\Settings::set...etopostprocess') !== '' is always true.
Loading history...
493
        $this->_maxSize = ($this->_maxSize > 0 ? ('AND r.size < '.($this->_maxSize * 1073741824)) : '');
494
        // Minimum size of releases in MB.
495
        $this->_minSize =
496
            (Settings::settingValue('..minsizetopostprocess') !== '') ? (int) Settings::settingValue('..minsizetopostprocess') : 100;
0 ignored issues
show
introduced by
The condition App\Models\Settings::set...etopostprocess') !== '' is always true.
Loading history...
497
        $this->_minSize = ($this->_minSize > 0 ? ('AND r.size > '.($this->_minSize * 1048576)) : '');
498
499
        // Use the alternate NNTP provider for downloading Message-ID's ?
500
        $this->_alternateNNTP = (int) Settings::settingValue('..alternate_nntp') === 1;
501
502
        $this->_ffMPEGDuration = Settings::settingValue('..ffmpeg_duration') !== '' ? (int) Settings::settingValue('..ffmpeg_duration') : 5;
0 ignored issues
show
introduced by
The condition App\Models\Settings::set...fmpeg_duration') !== '' is always true.
Loading history...
503
504
        $this->_addPAR2Files = (int) Settings::settingValue('..addpar2') !== 0;
505
506
        if (! Settings::settingValue('apps..ffmpegpath')) {
507
            $this->_processAudioSample = $this->_processThumbnails = $this->_processVideo = false;
508
        } else {
509
            $this->_processAudioSample = (int) Settings::settingValue('..saveaudiopreview') !== 0;
510
            $this->_processThumbnails = (int) Settings::settingValue('..processthumbnails') !== 0;
511
            $this->_processVideo = (int) Settings::settingValue('..processvideos') !== 0;
512
        }
513
514
        $this->_processJPGSample = (int) Settings::settingValue('..processjpg') !== 0;
515
        $this->_processMediaInfo = Settings::settingValue('apps..mediainfopath') !== '';
516
        $this->_processAudioInfo = $this->_processMediaInfo;
517
        $this->_processPasswords = (
518
            ((int) Settings::settingValue('..checkpasswordedrar') !== 0) &&
519
            Settings::settingValue('apps..unrarpath') !== ''
520
        );
521
522
        $this->_audioSavePath = NN_COVERS.'audiosample'.DS;
523
524
        $this->_audioFileRegex = '\.(AAC|AIFF|APE|AC3|ASF|DTS|FLAC|MKA|MKS|MP2|MP3|RA|OGG|OGM|W64|WAV|WMA)';
525
        $this->_ignoreBookRegex = '/\b(epub|lit|mobi|pdf|sipdf|html)\b.*\.rar(?!.{20,})/i';
526
        $this->_supportFileRegex = '/\.(vol\d{1,3}\+\d{1,3}|par2|srs|sfv|nzb';
527
        $this->_videoFileRegex = '\.(AVI|F4V|IFO|M1V|M2V|M4V|MKV|MOV|MP4|MPEG|MPG|MPGV|MPV|OGV|QT|RM|RMVB|TS|VOB|WMV)';
528
    }
529
530
    /**
531
     * Clear out the main temp path when done.
532
     */
533
    public function __destruct()
534
    {
535
        $this->_clearMainTmpPath();
536
    }
537
538
    /**
539
     * @param string $groupID
540
     * @param string $guidChar
541
     *
542
     * @throws \Exception
543
     */
544
    public function start($groupID = '', $guidChar = ''): void
545
    {
546
        $this->_setMainTempPath($guidChar, $groupID);
547
548
        // Fetch all the releases to work on.
549
        $this->_fetchReleases($groupID, $guidChar);
550
551
        // Check if we have releases to work on.
552
        if ($this->_totalReleases > 0) {
553
            // Echo start time and process description.
554
            $this->_echoDescription();
555
556
            $this->_processReleases();
557
        }
558
    }
559
560
    /**
561
     * @var string Main temp path to work on.
562
     */
563
    protected $_mainTmpPath;
564
565
    /**
566
     * @var string Temp path for current release.
567
     */
568
    protected $tmpPath;
569
570
    /**
571
     * @param $guidChar
572
     * @param string $groupID
573
     * @throws \RuntimeException
574
     * @throws \Exception
575
     */
576
    protected function _setMainTempPath(&$guidChar, &$groupID = ''): void
577
    {
578
        // Set up the temporary files folder location.
579
        $this->_mainTmpPath = (string) Settings::settingValue('..tmpunrarpath');
580
581
        // Check if it ends with a dir separator.
582
        if (! preg_match('/[\/\\\\]$/', $this->_mainTmpPath)) {
583
            $this->_mainTmpPath .= DS;
584
        }
585
586
        // If we are doing per group, use the groupID has a inner path, so other scripts don't delete the files we are working on.
587
        if ($groupID !== '') {
588
            $this->_mainTmpPath .= ($groupID.DS);
589
        } elseif ($guidChar !== '') {
590
            $this->_mainTmpPath .= ($guidChar.DS);
591
        }
592
593
        if (! File::isDirectory($this->_mainTmpPath)) {
594
            if (! File::makeDirectory($this->_mainTmpPath, 0777, true, true) && ! File::isDirectory($this->_mainTmpPath)) {
595
                throw new \RuntimeException(sprintf('Directory "%s" was not created', $this->_mainTmpPath));
596
            }
597
        }
598
599
        if (! File::isDirectory($this->_mainTmpPath)) {
600
            throw new \RuntimeException('Could not create the tmpunrar folder ('.$this->_mainTmpPath.')');
601
        }
602
603
        $this->_clearMainTmpPath();
604
605
        $this->tmpPath = $this->_mainTmpPath;
606
    }
607
608
    /**
609
     * Clear out old folders/files from the main temp folder.
610
     */
611
    protected function _clearMainTmpPath(): void
612
    {
613
        if ($this->_mainTmpPath !== '') {
614
            $this->_recursivePathDelete(
615
                $this->_mainTmpPath,
616
                // These are folders we don't want to delete.
617
                [
618
                    // This is the actual temp folder.
619
                    $this->_mainTmpPath,
620
                ]
621
            );
622
        }
623
    }
624
625
    /**
626
     * Get all releases that need to be processed.
627
     *
628
     * @param int|string $groupID
629
     * @param string     $guidChar
630
     *
631
     * @void
632
     */
633
    protected function _fetchReleases($groupID, &$guidChar): void
634
    {
635
        $this->_releases = DB::select(
636
            sprintf(
637
                '
638
				SELECT r.id, r.id AS releases_id, r.guid, r.name, r.size, r.groups_id, r.nfostatus,
639
					r.fromname, r.completion, r.categories_id, r.searchname, r.predb_id,
640
					c.disablepreview
641
				FROM releases r
642
				LEFT JOIN categories c ON c.id = r.categories_id
643
				WHERE r.nzbstatus = 1
644
				%s %s %s %s
645
				AND r.passwordstatus BETWEEN -6 AND -1
646
				AND r.haspreview = -1
647
				AND c.disablepreview = 0
648
				ORDER BY r.passwordstatus ASC, r.postdate DESC
649
				LIMIT %d',
650
                $this->_maxSize,
651
                $this->_minSize,
652
                ($groupID === '' ? '' : 'AND r.groups_id = '.$groupID),
653
                ($guidChar === '' ? '' : 'AND r.leftguid = '.escapeString($guidChar)),
654
                $this->_queryLimit
655
            )
656
        );
657
658
        $this->_totalReleases = \count($this->_releases);
659
    }
660
661
    /**
662
     * Output the description and start time.
663
     *
664
     * @void
665
     */
666
    protected function _echoDescription(): void
667
    {
668
        if ($this->_totalReleases > 1 && $this->_echoCLI) {
669
            $this->_echo(
670
                PHP_EOL.
671
                'Additional post-processing, started at: '.
672
                now()->format('D M d, Y G:i a').
673
                PHP_EOL.
674
                'Downloaded: (xB) = yEnc article, f= Failed ;Processing: z = ZIP file, r = RAR file'.
675
                PHP_EOL.
676
                'Added: s = Sample image, j = JPEG image, A = Audio sample, a = Audio MediaInfo, v = Video sample'.
677
                PHP_EOL.
678
                'Added: m = Video MediaInfo, n = NFO, ^ = File details from inside the RAR/ZIP',
679
                'header'
680
            );
681
        }
682
    }
683
684
    /**
685
     * Loop through the releases, processing them 1 at a time.
686
     *
687
     * @throws \RuntimeException
688
     * @throws \Exception
689
     */
690
    protected function _processReleases(): void
691
    {
692
        foreach ($this->_releases as $this->_release) {
693
            $this->_echo(
694
                PHP_EOL.'['.$this->_release->id.']['.
695
                human_filesize($this->_release->size, 1).']',
696
                'primaryOver'
697
            );
698
699
            cli_set_process_title($this->_showCLIReleaseID.$this->_release->id);
700
701
            // Create folder to store temporary files.
702
            if (! $this->_createTempFolder()) {
703
                continue;
704
            }
705
706
            // Get NZB contents.
707
            if (! $this->_getNZBContents()) {
708
                continue;
709
            }
710
711
            // Reset the current release variables.
712
            $this->_resetReleaseStatus();
713
714
            // Go through the files in the NZB, get the amount of book files.
715
            $totalBooks = $this->_processNZBContents();
716
717
            // Check if this NZB is a large collection of books.
718
            $bookFlood = false;
719
            if ($totalBooks > 80 && ($totalBooks * 2) >= \count($this->_nzbContents)) {
720
                $bookFlood = true;
721
            }
722
723
            if ($this->_processPasswords || $this->_processThumbnails || $this->_processMediaInfo || $this->_processAudioInfo || $this->_processVideo
724
            ) {
725
726
                // Process usenet Message-ID downloads.
727
                $this->_processMessageIDDownloads();
728
729
                // Process compressed (RAR/ZIP) files inside the NZB.
730
                if (! $bookFlood && $this->_NZBHasCompressedFile) {
731
                    // Download the RARs/ZIPs, extract the files inside them and insert the file info into the DB.
732
                    $this->_processNZBCompressedFiles();
733
734
                    // Download rar/zip in reverse order, to get the last rar or zip file.
735
                    if ($this->_fetchLastFiles) {
736
                        $this->_processNZBCompressedFiles(true);
737
                    }
738
739
                    if (! $this->_releaseHasPassword) {
740
                        // Process the extracted files to get video/audio samples/etc.
741
                        $this->_processExtractedFiles();
742
                    }
743
                }
744
            }
745
746
            // Update the release to say we processed it.
747
            $this->_finalizeRelease();
748
749
            // Delete all files / folders for this release.
750
            $this->_recursivePathDelete($this->tmpPath);
751
        }
752
        if ($this->_echoCLI) {
753
            echo PHP_EOL;
754
        }
755
    }
756
757
    /**
758
     * Deletes files and folders recursively.
759
     *
760
     * @param string $path          Path to a folder or file.
761
     * @param string[] $ignoredFolders array with paths to folders to ignore.
762
     *
763
     * @void
764
     */
765
    protected function _recursivePathDelete($path, $ignoredFolders = []): void
766
    {
767
        if (File::isDirectory($path)) {
768
            if (\in_array($path, $ignoredFolders, false)) {
769
                return;
770
            }
771
            foreach (File::allFiles($path) as $file) {
772
                $this->_recursivePathDelete($file, $ignoredFolders);
773
            }
774
775
            File::deleteDirectory($path);
776
        } elseif (File::isFile($path)) {
777
            File::delete($path);
778
        }
779
    }
780
781
    /**
782
     * Create a temporary storage folder for the current release.
783
     *
784
     * @return bool
785
     */
786
    protected function _createTempFolder(): bool
787
    {
788
        // Per release defaults.
789
        $this->tmpPath = $this->_mainTmpPath.$this->_release->guid.DS;
790
        if (! File::isDirectory($this->tmpPath)) {
791
            if (! File::makeDirectory($this->tmpPath, 0777, true, false) && ! File::isDirectory($this->tmpPath)) {
792
                $this->_echo('Unable to create directory: '.$this->tmpPath, 'warning');
793
794
                return $this->_decrementPasswordStatus();
795
            }
796
        }
797
798
        return true;
799
    }
800
801
    /**
802
     * Get list of contents inside a release's NZB file.
803
     *
804
     * @return bool
805
     */
806
    protected function _getNZBContents(): bool
807
    {
808
        $nzbPath = $this->_nzb->NZBPath($this->_release->guid);
809
        if ($nzbPath === false) {
810
            $this->_echo('NZB not found for GUID: '.$this->_release->guid, 'warning');
811
812
            return $this->_decrementPasswordStatus();
813
        }
814
815
        $nzbContents = Utility::unzipGzipFile($nzbPath);
816
        if (! $nzbContents) {
0 ignored issues
show
introduced by
The condition $nzbContents is always false.
Loading history...
817
            $this->_echo('NZB is empty or broken for GUID: '.$this->_release->guid, 'warning');
818
819
            return $this->_decrementPasswordStatus();
820
        }
821
822
        // Get a list of files in the nzb.
823
        $this->_nzbContents = $this->_nzb->nzbFileList($nzbContents, ['no-file-key' => false, 'strip-count' => true]);
824
        if (\count($this->_nzbContents) === 0) {
825
            $this->_echo('NZB is potentially broken for GUID: '.$this->_release->guid, 'warning');
826
827
            return $this->_decrementPasswordStatus();
828
        }
829
        // Sort keys.
830
        ksort($this->_nzbContents, SORT_NATURAL);
831
832
        return true;
833
    }
834
835
    /**
836
     * Decrement password status for the current release.
837
     *
838
     * @return false
839
     */
840
    protected function _decrementPasswordStatus(): bool
841
    {
842
        Release::whereId($this->_release->id)->decrement('passwordstatus');
843
844
        return false;
845
    }
846
847
    /**
848
     * Current file we are working on inside a NZB.
849
     * @var array
850
     */
851
    protected $_currentNZBFile;
852
853
    /**
854
     * Does the current NZB contain a compressed (RAR/ZIP) file?
855
     * @var bool
856
     */
857
    protected $_NZBHasCompressedFile;
858
859
    /**
860
     * Process the files inside the NZB, find Message-ID's to download.
861
     * If we find files with book extensions, return the amount.
862
     *
863
     * @return int
864
     */
865
    protected function _processNZBContents(): int
866
    {
867
        $totalBookFiles = 0;
868
        foreach ($this->_nzbContents as $this->_currentNZBFile) {
869
870
            // Check if it's not a nfo, nzb, par2 etc...
871
            if (preg_match($this->_supportFileRegex.'|nfo\b|inf\b|ofn\b)($|[ ")\]-])(?!.{20,})/i', $this->_currentNZBFile['title'])) {
872
                continue;
873
            }
874
875
            // Check if it's a rar/zip.
876
            if (! $this->_NZBHasCompressedFile &&
877
                preg_match(
878
                    '/\.(part\d+|r\d+|rar|0+|0*10?|zipr\d{2,3}|zipx?)(\s*\.rar)*($|[ ")\]-])|"[a-f0-9]{32}\.[1-9]\d{1,2}".*\(\d+\/\d{2,}\)$/i',
879
                    $this->_currentNZBFile['title']
880
                )
881
            ) {
882
                $this->_NZBHasCompressedFile = true;
883
            }
884
885
            // Look for a video sample, make sure it's not an image.
886
            if ($this->_processThumbnails && empty($this->_sampleMessageIDs) && stripos($this->_currentNZBFile['title'], 'sample') !== false && ! preg_match('/\.jpe?g$/i', $this->_currentNZBFile['title']) && isset($this->_currentNZBFile['segments'])
887
            ) {
888
                // Get the amount of segments for this file.
889
                $segCount = (\count($this->_currentNZBFile['segments']) - 1);
890
                // If it's more than 1 try to get up to the site specified value of segments.
891
                for ($i = 0; $i < $this->_segmentsToDownload; $i++) {
892
                    if ($i > $segCount) {
893
                        break;
894
                    }
895
                    $this->_sampleMessageIDs[] = (string) $this->_currentNZBFile['segments'][$i];
896
                }
897
            }
898
899
            // Look for a JPG picture, make sure it's not a CD cover.
900
            if ($this->_processJPGSample && empty($this->_JPGMessageIDs) && ! preg_match('/flac|lossless|mp3|music|inner-sanctum|sound/i', $this->_releaseGroupName) && preg_match('/\.jpe?g[. ")\]]/i', $this->_currentNZBFile['title']) && isset($this->_currentNZBFile['segments'])
901
            ) {
902
                // Get the amount of segments for this file.
903
                $segCount = (\count($this->_currentNZBFile['segments']) - 1);
904
                // If it's more than 1 try to get up to the site specified value of segments.
905
                for ($i = 0; $i < $this->_segmentsToDownload; $i++) {
906
                    if ($i > $segCount) {
907
                        break;
908
                    }
909
                    $this->_JPGMessageIDs[] = (string) $this->_currentNZBFile['segments'][$i];
910
                }
911
            }
912
913
            // Look for a video file, make sure it's not a sample, for MediaInfo.
914
            if ($this->_processMediaInfo && empty($this->_MediaInfoMessageIDs) && stripos($this->_currentNZBFile['title'], 'sample') !== false && preg_match('/'.$this->_videoFileRegex.'[. ")\]]/i', $this->_currentNZBFile['title']) && isset($this->_currentNZBFile['segments'][0])
915
            ) {
916
                $this->_MediaInfoMessageIDs = (string) $this->_currentNZBFile['segments'][0];
917
            }
918
919
            // Look for a audio file.
920
            if ($this->_processAudioInfo && empty($this->_AudioInfoMessageIDs) && preg_match('/'.$this->_audioFileRegex.'[. ")\]]/i', $this->_currentNZBFile['title'], $type) && isset($this->_currentNZBFile['segments'])
921
            ) {
922
                // Get the extension.
923
                $this->_AudioInfoExtension = $type[1];
924
                $this->_AudioInfoMessageIDs = (string) $this->_currentNZBFile['segments'][0];
925
            }
926
927
            // Some releases contain many books, increment this to ignore them later.
928
            if (preg_match($this->_ignoreBookRegex, $this->_currentNZBFile['title'])) {
929
                $totalBookFiles++;
930
            }
931
        }
932
933
        return $totalBookFiles;
934
    }
935
936
    /**
937
     * List of message-id's we have tried for rar/zip files.
938
     * @var array
939
     */
940
    protected $_triedCompressedMids = [];
941
942
    /**
943
     * @param bool $reverse
944
     * @throws \Exception
945
     */
946
    protected function _processNZBCompressedFiles($reverse = false): void
947
    {
948
        $this->_reverse = $reverse;
949
950
        if ($this->_reverse) {
951
            if (! krsort($this->_nzbContents)) {
952
                return;
953
            }
954
        } else {
955
            $this->_triedCompressedMids = [];
956
        }
957
958
        $failed = $downloaded = 0;
959
        // Loop through the files, attempt to find if password-ed and files. Starting with what not to process.
960
        foreach ($this->_nzbContents as $nzbFile) {
961
            // TODO change this to max calculated size, as segments vary in size greatly.
962
            if ($downloaded >= $this->_maximumRarSegments) {
963
                break;
964
            }
965
966
            if ($failed >= $this->_maximumRarPasswordChecks) {
967
                break;
968
            }
969
970
            if ($this->_releaseHasPassword) {
971
                $this->_echo('Skipping processing of rar '.$nzbFile['title'].' it has a password.', 'primaryOver');
972
                break;
973
            }
974
975
            // Probably not a rar/zip.
976
            if (! preg_match(
977
                '/\.(part\d+|r\d+|rar|0+|0*10?|zipr\d{2,3}|zipx?)(\s*\.rar)*($|[ ")\]-])|"[a-f0-9]{32}\.[1-9]\d{1,2}".*\(\d+\/\d{2,}\)$/i',
978
                $nzbFile['title']
979
            )
980
            ) {
981
                continue;
982
            }
983
984
            // Get message-id's for the rar file.
985
            $segCount = (\count($nzbFile['segments']) - 1);
986
            $mID = [];
987
            for ($i = 0; $i < $this->_maximumRarSegments; $i++) {
988
                if ($i > $segCount) {
989
                    break;
990
                }
991
                $segment = (string) $nzbFile['segments'][$i];
992
                if (! $this->_reverse) {
993
                    $this->_triedCompressedMids[] = $segment;
994
                } elseif (\in_array($segment, $this->_triedCompressedMids, false)) {
995
                    // We already downloaded this file.
996
                    continue 2;
997
                }
998
                $mID[] = $segment;
999
            }
1000
            // Nothing to download.
1001
            if (empty($mID)) {
1002
                continue;
1003
            }
1004
1005
            // Download the article(s) from usenet.
1006
            $fetchedBinary = $this->_nntp->getMessages($this->_releaseGroupName, $mID, $this->_alternateNNTP);
1007
            if ($this->_nntp->isError($fetchedBinary)) {
1008
                $fetchedBinary = false;
1009
            }
1010
1011
            if ($fetchedBinary !== false) {
1012
1013
                // Echo we downloaded compressed file.
1014
                if ($this->_echoCLI) {
1015
                    $this->_echo('(cB)', 'primaryOver');
1016
                }
1017
1018
                $downloaded++;
1019
1020
                // Process the compressed file.
1021
                $decompressed = $this->_processCompressedData($fetchedBinary);
1022
1023
                if ($decompressed || $this->_releaseHasPassword) {
1024
                    break;
1025
                }
1026
            } else {
1027
                $failed++;
1028
                if ($this->_echoCLI) {
1029
                    $this->_echo('f('.$failed.')', 'warningOver');
1030
                }
1031
            }
1032
        }
1033
    }
1034
1035
    /**
1036
     * Check if the data is a ZIP / RAR file, extract files, get file info.
1037
     *
1038
     * @param string $compressedData
1039
     *
1040
     * @return bool
1041
     * @throws \Exception
1042
     */
1043
    protected function _processCompressedData(&$compressedData): bool
1044
    {
1045
        $this->_compressedFilesChecked++;
1046
        // Give the data to archive info so it can check if it's a rar.
1047
        if (! $this->_archiveInfo->setData($compressedData, true)) {
1048
            if (config('app.debug') === true) {
1049
                $this->_debug('Data is probably not RAR or ZIP.');
1050
            }
1051
1052
            return false;
1053
        }
1054
1055
        // Check if there's an error.
1056
        if ($this->_archiveInfo->error !== '') {
1057
            if (config('app.debug') === true) {
1058
                $this->_debug('ArchiveInfo Error: '.$this->_archiveInfo->error);
1059
            }
1060
1061
            return false;
1062
        }
1063
1064
        try {
1065
            // Get a summary of the compressed file.
1066
            $dataSummary = $this->_archiveInfo->getSummary(true);
1067
        } catch (\Exception $exception) {
1068
            //Log the exception and continue to next item
1069
            if (config('app.debug') === true) {
1070
                Log::warning($exception->getTraceAsString());
1071
            }
1072
1073
            return false;
1074
        }
1075
1076
        // Check if the compressed file is encrypted.
1077
        if (! empty($this->_archiveInfo->isEncrypted) || (isset($dataSummary['is_encrypted']) && (int) $dataSummary['is_encrypted'] !== 0)) {
1078
            if (config('app.debug') === true) {
1079
                $this->_debug('ArchiveInfo: Compressed file has a password.');
1080
            }
1081
            $this->_releaseHasPassword = true;
1082
            $this->_passwordStatus[] = Releases::PASSWD_RAR;
1083
1084
            return false;
1085
        }
1086
1087
        switch ($dataSummary['main_type']) {
1088
                case ArchiveInfo::TYPE_RAR:
1089
                    if ($this->_echoCLI) {
1090
                        $this->_echo('r', 'primaryOver');
1091
                    }
1092
1093
                    if (! $this->_extractUsingRarInfo && $this->_unrarPath !== false) {
1094
                        $fileName = $this->tmpPath.uniqid('', true).'.rar';
1095
                        File::put($fileName, $compressedData);
1096
                        runCmd($this->_killString.$this->_unrarPath.'" e -ai -ep -c- -id -inul -kb -or -p- -r -y "'.$fileName.'" "'.$this->tmpPath.'unrar/"');
1097
                        File::delete($fileName);
1098
                    }
1099
                    break;
1100
                case ArchiveInfo::TYPE_ZIP:
1101
                    if ($this->_echoCLI) {
1102
                        $this->_echo('z', 'primaryOver');
1103
                    }
1104
1105
                    if (! $this->_extractUsingRarInfo && $this->_7zipPath !== false) {
1106
                        $fileName = $this->tmpPath.uniqid('', true).'.zip';
1107
                        File::put($fileName, $compressedData);
1108
                        runCmd($this->_killString.$this->_7zipPath.'" x "'.$fileName.'" -bd -y -o"'.$this->tmpPath.'unzip/"');
1109
                        File::delete($fileName);
1110
                    }
1111
                    break;
1112
                default:
1113
                    return false;
1114
            }
1115
1116
        return $this->_processCompressedFileList();
1117
    }
1118
1119
    /**
1120
     * Get a list of all files in the compressed file, add the file info to the DB.
1121
     *
1122
     * @return bool
1123
     * @throws \Exception
1124
     */
1125
    protected function _processCompressedFileList(): bool
1126
    {
1127
        // Get a list of files inside the Compressed file.
1128
        $files = $this->_archiveInfo->getArchiveFileList();
1129
        if (! \is_array($files) || \count($files) === 0) {
0 ignored issues
show
introduced by
The condition is_array($files) is always true.
Loading history...
1130
            return false;
1131
        }
1132
1133
        // Loop through the files.
1134
        foreach ($files as $file) {
1135
            if ($this->_releaseHasPassword) {
1136
                break;
1137
            }
1138
1139
            if (isset($file['name'])) {
1140
                if (isset($file['error'])) {
1141
                    if (config('app.debug') === true) {
1142
                        $this->_debug("Error: {$file['error']} (in: {$file['source']})");
1143
                    }
1144
                    continue;
1145
                }
1146
1147
                if (isset($file['pass']) && $file['pass'] === true) {
1148
                    $this->_releaseHasPassword = true;
1149
                    $this->_passwordStatus[] = Releases::PASSWD_RAR;
1150
                    break;
1151
                }
1152
1153
                if ($this->_innerFileBlacklist !== false && preg_match($this->_innerFileBlacklist, $file['name'])) {
0 ignored issues
show
Bug introduced by
It seems like $this->_innerFileBlacklist can also be of type true; however, parameter $pattern of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1153
                if ($this->_innerFileBlacklist !== false && preg_match(/** @scrutinizer ignore-type */ $this->_innerFileBlacklist, $file['name'])) {
Loading history...
1154
                    $this->_releaseHasPassword = true;
1155
                    $this->_passwordStatus[] = Releases::PASSWD_POTENTIAL;
1156
                    break;
1157
                }
1158
1159
                $fileName = [];
1160
                if (preg_match('/[^\/\\\\]*\.[a-zA-Z0-9]*$/', $file['name'], $fileName)) {
1161
                    $fileName = $fileName[0];
1162
                } else {
1163
                    $fileName = '';
1164
                }
1165
1166
                if ($this->_extractUsingRarInfo) {
1167
                    // Extract files from the rar.
1168
                    if (isset($file['compressed']) && (int) $file['compressed'] === 0) {
1169
                        File::put(
1170
                            $this->tmpPath.random_int(10, 999999).'_'.$fileName,
1171
                            $this->_archiveInfo->getFileData($file['name'], $file['source'])
0 ignored issues
show
Bug introduced by
It seems like $this->_archiveInfo->get...ame'], $file['source']) can also be of type false; however, parameter $contents of Illuminate\Support\Facades\File::put() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1171
                            /** @scrutinizer ignore-type */ $this->_archiveInfo->getFileData($file['name'], $file['source'])
Loading history...
1172
                        );
1173
                    } // If the files are compressed, use a binary extractor.
1174
                    else {
1175
                        $this->_archiveInfo->extractFile($file['name'], $this->tmpPath.random_int(10, 999999).'_'.$fileName);
1176
                    }
1177
                }
1178
            }
1179
            $this->_addFileInfo($file);
1180
        }
1181
        if ($this->_addedFileInfo > 0) {
1182
            $this->sphinx->updateRelease($this->_release->id);
1183
        }
1184
1185
        return $this->_totalFileInfo > 0;
1186
    }
1187
1188
    /**
1189
     * @param $file
1190
     * @throws \Exception
1191
     */
1192
    protected function _addFileInfo(&$file): void
1193
    {
1194
        // Don't add rar/zip files to the DB.
1195
        if (! isset($file['error']) && isset($file['source']) &&
1196
            ! preg_match($this->_supportFileRegex.'|part\d+|r\d{1,3}|zipr\d{2,3}|\d{2,3}|zipx|zip|rar)(\s*\.rar)?$/i', $file['name'])
1197
        ) {
1198
1199
            // Cache the amount of files we find in the RAR or ZIP, return this to say we did find RAR or ZIP content.
1200
            // This is so we don't download more RAR or ZIP files for no reason.
1201
            $this->_totalFileInfo++;
1202
1203
            /* Check if we already have the file or not.
1204
             * Also make sure we don't add too many files, some releases have 100's of files, like PS3 releases.
1205
             */
1206
            if ($this->_addedFileInfo < 11 && ReleaseFile::query()->where(['releases_id' => $this->_release->id, 'name' => $file['name'], 'size' => $file
1207
                ['size'], ])->first() === null) {
1208
                if (ReleaseFile::addReleaseFiles($this->_release->id, $file['name'], $file['size'], $file['date'], $file['pass'], '', $file['crc32'] ?? '')) {
1209
                    $this->_addedFileInfo++;
1210
1211
                    if ($this->_echoCLI) {
1212
                        $this->_echo('^', 'primaryOver');
1213
                    }
1214
1215
                    // Check for "codec spam"
1216
                    if (preg_match('/alt\.binaries\.movies($|\.divx$)/', $this->_releaseGroupName) &&
1217
                        preg_match('/[\/\\\\]Codec[\/\\\\]Setup\.exe$/i', $file['name'])
1218
                    ) {
1219
                        if (config('app.debug') === true) {
1220
                            $this->_debug('Codec spam found, setting release to potentially passworded.');
1221
                        }
1222
                        $this->_releaseHasPassword = true;
1223
                        $this->_passwordStatus[] = Releases::PASSWD_POTENTIAL;
1224
                    } //Run a PreDB filename check on insert to try and match the release
1225
                    elseif ($file['name'] !== '' && strpos($file['name'], '.') !== 0) {
1226
                        $this->_release['filename'] = $file['name'];
1227
                        $this->_release['releases_id'] = $this->_release->id;
1228
                        $this->_nameFixer->matchPreDbFiles($this->_release, 1, 1, true);
1229
                    }
1230
                }
1231
            }
1232
        }
1233
    }
1234
1235
    /**
1236
     * Go through all the extracted files in the temp folder and process them.
1237
     *
1238
     * @throws \Exception
1239
     */
1240
    protected function _processExtractedFiles(): void
1241
    {
1242
        $nestedLevels = 0;
1243
1244
        // Go through all the files in the temp folder, look for compressed files, extract them and the nested ones.
1245
        while ($nestedLevels < $this->_maxNestedLevels) {
1246
1247
            // Break out if we checked more than x compressed files.
1248
            if ($this->_compressedFilesChecked >= self::maxCompressedFilesToCheck) {
1249
                break;
1250
            }
1251
1252
            $foundCompressedFile = false;
1253
1254
            // Get all the compressed files in the temp folder.
1255
            $files = $this->_getTempDirectoryContents('/.*\.([rz]\d{2,}|rar|zipx?|0{0,2}1)($|[^a-z0-9])/i');
1256
1257
            if ($files instanceof \Traversable) {
1258
                foreach ($files as $file) {
1259
1260
                    // Check if the file exists.
1261
                    if (File::isFile($file[0])) {
1262
                        $rarData = @File::get($file[0]);
1263
                        if ($rarData !== false) {
1264
                            $this->_processCompressedData($rarData);
1265
                            $foundCompressedFile = true;
1266
                        }
1267
                        File::delete($file[0]);
1268
                    }
1269
                }
1270
            }
1271
1272
            // If we found no compressed files, break out.
1273
            if (! $foundCompressedFile) {
1274
                break;
1275
            }
1276
1277
            $nestedLevels++;
1278
        }
1279
1280
        $fileType = [];
1281
1282
        // Get all the remaining files in the temp dir.
1283
        $files = $this->_getTempDirectoryContents();
1284
        foreach ($files as $file) {
1285
            $file = $file->getPathname();
1286
1287
            // Skip /. and /..
1288
            if (preg_match('/[\/\\\\]\.{1,2}$/', $file)) {
1289
                continue;
1290
            }
1291
1292
            if (File::isFile($file)) {
1293
1294
                    // Process PAR2 files.
1295
                if (! $this->_foundPAR2Info && preg_match('/\.par2$/', $file)) {
1296
                    $this->_siftPAR2Info($file);
1297
                } // Process NFO files.
1298
                elseif ($this->_releaseHasNoNFO && preg_match('/(\.(nfo|inf|ofn)|info\.txt)$/i', $file)) {
1299
                    $this->_processNfoFile($file);
1300
                } // Process audio files.
1301
                elseif (
1302
                        (! $this->_foundAudioInfo || ! $this->_foundAudioSample) &&
1303
                        preg_match('/(.*)'.$this->_audioFileRegex.'$/i', $file, $fileType)
1304
                    ) {
1305
                    // Try to get audio sample/audio media info.
1306
                    File::move($file, $this->tmpPath.'audiofile.'.$fileType[2]);
1307
                    $this->_getAudioInfo($this->tmpPath.'audiofile.'.$fileType[2], $fileType[2]);
1308
                    File::delete($this->tmpPath.'audiofile.'.$fileType[2]);
1309
                } // Process JPG files.
1310
                elseif (! $this->_foundJPGSample && preg_match('/\.jpe?g$/i', $file)) {
1311
                    $this->_getJPGSample($file);
1312
                    File::delete($file);
1313
                } // Video sample // video clip // video media info.
1314
                elseif ((! $this->_foundSample || ! $this->_foundVideo || ! $this->_foundMediaInfo) &&
1315
                        preg_match('/(.*)'.$this->_videoFileRegex.'$/i', $file)
1316
                    ) {
1317
                    $this->_processVideoFile($file);
1318
                }
1319
1320
                // Check file's magic info.
1321
                else {
1322
                    $output = Utility::fileInfo($file);
1323
                    if (! empty($output)) {
1324
                        switch (true) {
1325
1326
                                case ! $this->_foundJPGSample && preg_match('/^JPE?G/i', $output):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1327
                                    $this->_getJPGSample($file);
1328
                                    File::delete($file);
1329
                                    break;
1330
1331
                                case
0 ignored issues
show
Coding Style introduced by
As per coding-style, case should be followed by a single space.

As per the PSR-2 coding standard, there must be a space after the case keyword, instead of the test immediately following it.

switch (true) {
    case!isset($a):  //wrong
        doSomething();
        break;
    case !isset($b):  //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1332
                                    (! $this->_foundMediaInfo || ! $this->_foundSample || ! $this->_foundVideo)
1333
                                    && preg_match('/Matroska data|MPEG v4|MPEG sequence, v2|\WAVI\W/i', $output):
1334
                                    $this->_processVideoFile($file);
1335
                                    break;
1336
1337
                                case
0 ignored issues
show
Coding Style introduced by
As per coding-style, case should be followed by a single space.

As per the PSR-2 coding standard, there must be a space after the case keyword, instead of the test immediately following it.

switch (true) {
    case!isset($a):  //wrong
        doSomething();
        break;
    case !isset($b):  //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1338
                                    (! $this->_foundAudioSample || ! $this->_foundAudioInfo) &&
1339
                                    preg_match('/^FLAC|layer III|Vorbis audio/i', $output, $fileType):
1340
                                    switch ($fileType[0]) {
1341
                                        case 'FLAC':
1342
                                            $fileType = 'FLAC';
1343
                                            break;
1344
                                        case 'layer III':
1345
                                            $fileType = 'MP3';
1346
                                            break;
1347
                                        case 'Vorbis audio':
1348
                                            $fileType = 'OGG';
1349
                                            break;
1350
                                    }
1351
                                    File::move($file, $this->tmpPath.'audiofile.'.$fileType);
1352
                                    $this->_getAudioInfo($this->tmpPath.'audiofile.'.$fileType, $fileType);
1353
                                    File::delete($this->tmpPath.'audiofile.'.$fileType);
1354
                                    break;
1355
1356
                                case ! $this->_foundPAR2Info && stripos($output, 'Parity') === 0:
1357
                                    $this->_siftPAR2Info($file);
1358
                                    break;
1359
                            }
1360
                    }
1361
                }
1362
            }
1363
        }
1364
    }
1365
1366
    /**
1367
     * Download all binaries from usenet and form samples / get media info / etc from them.
1368
     *
1369
     * @void
1370
     * @throws \Exception
1371
     */
1372
    protected function _processMessageIDDownloads(): void
1373
    {
1374
        $this->_processSampleMessageIDs();
1375
        $this->_processMediaInfoMessageIDs();
1376
        $this->_processAudioInfoMessageIDs();
1377
        $this->_processJPGMessageIDs();
1378
    }
1379
1380
    /**
1381
     * Download and process binaries for sample videos.
1382
     *
1383
     * @void
1384
     * @throws \Exception
1385
     */
1386
    protected function _processSampleMessageIDs(): void
1387
    {
1388
        // Download and process sample image.
1389
        if (! $this->_foundSample || ! $this->_foundVideo) {
1390
            if (! empty($this->_sampleMessageIDs)) {
1391
1392
                // Download it from usenet.
1393
                $sampleBinary = $this->_nntp->getMessages($this->_releaseGroupName, $this->_sampleMessageIDs, $this->_alternateNNTP);
1394
                if ($this->_nntp->isError($sampleBinary)) {
1395
                    $sampleBinary = false;
1396
                }
1397
1398
                if ($sampleBinary !== false) {
1399
                    if ($this->_echoCLI) {
1400
                        $this->_echo('(sB)', 'primaryOver');
1401
                    }
1402
1403
                    // Check if it's more than 40 bytes.
1404
                    if (\strlen($sampleBinary) > 40) {
1405
                        $fileLocation = $this->tmpPath.'sample_'.random_int(0, 99999).'.avi';
1406
                        // Try to create the file.
1407
                        File::put($fileLocation, $sampleBinary);
1408
1409
                        // Try to get a sample picture.
1410
                        if (! $this->_foundSample) {
1411
                            $this->_foundSample = $this->_getSample($fileLocation);
1412
                        }
1413
1414
                        // Try to get a sample video.
1415
                        if (! $this->_foundVideo) {
1416
                            $this->_foundVideo = $this->_getVideo($fileLocation);
1417
                        }
1418
                    }
1419
                } elseif ($this->_echoCLI) {
1420
                    $this->_echo('f', 'warningOver');
1421
                }
1422
            }
1423
        }
1424
    }
1425
1426
    /**
1427
     * Download and process binaries for media info from videos.
1428
     *
1429
     * @void
1430
     * @throws \Exception
1431
     */
1432
    protected function _processMediaInfoMessageIDs(): void
1433
    {
1434
        // Download and process mediainfo. Also try to get a sample if we didn't get one yet.
1435
        if (! $this->_foundMediaInfo || ! $this->_foundSample || ! $this->_foundVideo) {
1436
            if (! $this->_foundMediaInfo && ! empty($this->_MediaInfoMessageIDs)) {
1437
1438
                // Try to download it from usenet.
1439
                $mediaBinary = $this->_nntp->getMessages($this->_releaseGroupName, $this->_MediaInfoMessageIDs, $this->_alternateNNTP);
1440
                if ($this->_nntp->isError($mediaBinary)) {
1441
                    // If error set it to false.
1442
                    $mediaBinary = false;
1443
                }
1444
1445
                if ($mediaBinary !== false) {
1446
                    if ($this->_echoCLI) {
1447
                        $this->_echo('(mB)', 'primaryOver');
1448
                    }
1449
1450
                    // If it's more than 40 bytes...
1451
                    if (\strlen($mediaBinary) > 40) {
1452
                        $fileLocation = $this->tmpPath.'media.avi';
1453
                        // Create a file on the disk with it.
1454
                        File::put($fileLocation, $mediaBinary);
1455
1456
                        // Try to get media info.
1457
                        if (! $this->_foundMediaInfo) {
1458
                            $this->_foundMediaInfo = $this->_getMediaInfo($fileLocation);
1459
                        }
1460
1461
                        // Try to get a sample picture.
1462
                        if (! $this->_foundSample) {
1463
                            $this->_foundSample = $this->_getSample($fileLocation);
1464
                        }
1465
1466
                        // Try to get a sample video.
1467
                        if (! $this->_foundVideo) {
1468
                            $this->_foundVideo = $this->_getVideo($fileLocation);
1469
                        }
1470
                    }
1471
                } elseif ($this->_echoCLI) {
1472
                    $this->_echo('f', 'warningOver');
1473
                }
1474
            }
1475
        }
1476
    }
1477
1478
    /**
1479
     * Download and process binaries for media info from songs.
1480
     *
1481
     * @void
1482
     * @throws \Exception
1483
     */
1484
    protected function _processAudioInfoMessageIDs(): void
1485
    {
1486
        // Download audio file, use media info to try to get the artist / album.
1487
        if (! $this->_foundAudioInfo || ! $this->_foundAudioSample) {
1488
            if (! empty($this->_AudioInfoMessageIDs)) {
1489
                // Try to download it from usenet.
1490
                $audioBinary = $this->_nntp->getMessages($this->_releaseGroupName, $this->_AudioInfoMessageIDs, $this->_alternateNNTP);
1491
                if ($this->_nntp->isError($audioBinary)) {
1492
                    $audioBinary = false;
1493
                }
1494
1495
                if ($audioBinary !== false) {
1496
                    if ($this->_echoCLI) {
1497
                        $this->_echo('(aB)', 'primaryOver');
1498
                    }
1499
1500
                    $fileLocation = $this->tmpPath.'audio.'.$this->_AudioInfoExtension;
1501
                    // Create a file with it.
1502
                    File::put($fileLocation, $audioBinary);
1503
1504
                    // Try to get media info / sample of the audio file.
1505
                    $this->_getAudioInfo($fileLocation, $this->_AudioInfoExtension);
1506
                } elseif ($this->_echoCLI) {
1507
                    $this->_echo('f', 'warningOver');
1508
                }
1509
            }
1510
        }
1511
    }
1512
1513
    /**
1514
     * Download and process binaries for JPG pictures.
1515
     *
1516
     * @void
1517
     * @throws \Exception
1518
     */
1519
    protected function _processJPGMessageIDs(): void
1520
    {
1521
        // Download JPG file.
1522
        if (! $this->_foundJPGSample && ! empty($this->_JPGMessageIDs)) {
1523
1524
            // Try to download it.
1525
            $jpgBinary = $this->_nntp->getMessages($this->_releaseGroupName, $this->_JPGMessageIDs, $this->_alternateNNTP);
1526
            if ($this->_nntp->isError($jpgBinary)) {
1527
                $jpgBinary = false;
1528
            }
1529
1530
            if ($jpgBinary !== false) {
1531
                if ($this->_echoCLI) {
1532
                    $this->_echo('(jB)', 'primaryOver');
1533
                }
1534
1535
                // Try to create a file with it.
1536
                File::put($this->tmpPath.'samplepicture.jpg', $jpgBinary);
1537
1538
                // Try to resize and move it.
1539
                $this->_foundJPGSample = (
1540
                $this->_releaseImage->saveImage(
1541
                    $this->_release->guid.'_thumb',
1542
                    $this->tmpPath.'samplepicture.jpg',
1543
                    $this->_releaseImage->jpgSavePath,
1544
                    650,
1545
                    650
1546
                ) === 1
1547
                );
1548
1549
                if ($this->_foundJPGSample) {
1550
                    // Update the DB to say we got it.
1551
                    Release::query()->where('id', $this->_release->id)->update(['jpgstatus' => 1]);
1552
1553
                    if ($this->_echoCLI) {
1554
                        $this->_echo('j', 'primaryOver');
1555
                    }
1556
                }
1557
1558
                File::delete($this->tmpPath.'samplepicture.jpg');
1559
            } elseif ($this->_echoCLI) {
1560
                $this->_echo('f', 'warningOver');
1561
            }
1562
        }
1563
    }
1564
1565
    /**
1566
     * Update the release to say we processed it.
1567
     */
1568
    protected function _finalizeRelease(): void
1569
    {
1570
        $vSQL = $jSQL = '';
1571
        $iSQL = ', haspreview = 0';
1572
1573
        // If samples exist from previous runs, set flags.
1574
        if (File::isFile($this->_releaseImage->imgSavePath.$this->_release->guid.'_thumb.jpg')) {
1575
            $iSQL = ', haspreview = 1';
1576
        }
1577
1578
        if (File::isFile($this->_releaseImage->vidSavePath.$this->_release->guid.'.ogv')) {
1579
            $vSQL = ', videostatus = 1';
1580
        }
1581
1582
        if (File::isFile($this->_releaseImage->jpgSavePath.$this->_release->guid.'_thumb.jpg')) {
1583
            $jSQL = ', jpgstatus = 1';
1584
        }
1585
1586
        // Get the amount of files we found inside the RAR/ZIP files.
1587
1588
        $releaseFilesCount = ReleaseFile::whereReleasesId($this->_release->id)->count('releases_id');
1589
1590
        if ($releaseFilesCount === null) {
1591
            $releaseFilesCount = 0;
1592
        }
1593
1594
        $this->_passwordStatus = max($this->_passwordStatus);
1595
1596
        // Set the release to no password if password processing is off.
1597
        if (! $this->_processPasswords) {
1598
            $this->_releaseHasPassword = false;
1599
        }
1600
1601
        // If we failed to get anything from the RAR/ZIPs, decrement the passwordstatus, if the rar/zip has no password.
1602
        if (! $this->_releaseHasPassword && $this->_NZBHasCompressedFile && $releaseFilesCount === 0) {
1603
            $query = sprintf(
1604
                'UPDATE releases
1605
				SET passwordstatus = passwordstatus - 1, rarinnerfilecount = %d %s %s %s
1606
				WHERE id = %d',
1607
                $releaseFilesCount,
1608
                $iSQL,
1609
                $vSQL,
1610
                $jSQL,
1611
                $this->_release->id
1612
            );
1613
        } // Else update the release with the password status (if the admin enabled the setting).
1614
        else {
1615
            $query = sprintf(
1616
                'UPDATE releases
1617
				SET passwordstatus = %d, rarinnerfilecount = %d %s %s %s
1618
				WHERE id = %d',
1619
                ($this->_processPasswords ? $this->_passwordStatus : Releases::PASSWD_NONE),
1620
                $releaseFilesCount,
1621
                $iSQL,
1622
                $vSQL,
1623
                $jSQL,
1624
                $this->_release->id
1625
            );
1626
        }
1627
1628
        Release::fromQuery($query);
1629
    }
1630
1631
    /**
1632
     * @param string $pattern
1633
     * @param string $path
1634
     *
1635
     * @return bool|string|\Symfony\Component\Finder\SplFileInfo[]
1636
     */
1637
    protected function _getTempDirectoryContents($pattern = '', $path = '')
1638
    {
1639
        if ($path === '') {
1640
            $path = $this->tmpPath;
1641
        }
1642
1643
        $files = File::allFiles($path);
1644
        try {
1645
            if ($pattern !== '') {
1646
                $allFiles = [];
1647
                foreach ($files as $file) {
1648
                    if (preg_match($pattern, $file->getRelativePathname())) {
1649
                        $allFiles .= $file;
1650
                    }
1651
                }
1652
1653
                return $allFiles;
1654
            }
1655
1656
            return $files;
1657
        } catch (\Throwable $e) {
1658
            if (config('app.debug') === true) {
1659
                Log::error($e->getTraceAsString());
1660
                $this->_debug('ERROR: Could not open temp dir: '.$e->getMessage());
1661
            }
1662
1663
            return false;
1664
        }
1665
    }
1666
1667
    /**
1668
     * @param $fileLocation
1669
     * @param $fileExtension
1670
     * @return bool
1671
     * @throws \Exception
1672
     */
1673
    protected function _getAudioInfo($fileLocation, $fileExtension): bool
1674
    {
1675
        // Return values.
1676
        $retVal = $audVal = false;
1677
1678
        // Check if audio sample fetching is on.
1679
        if (! $this->_processAudioSample) {
1680
            $audVal = true;
1681
        }
1682
1683
        // Check if media info fetching is on.
1684
        if (! $this->_processAudioInfo) {
1685
            $retVal = true;
1686
        }
1687
1688
        $rQuery = Release::query()->where('proc_pp', '=', 0)->where('id', $this->_release->id)->select(['searchname', 'fromname', 'categories_id'])->first();
1689
1690
        $musicParent = (string) Category::MUSIC_ROOT;
1691
        if ($rQuery === null || ! preg_match(
1692
                sprintf(
1693
                    '/%d\d{3}|%d|%d|%d/',
1694
                    $musicParent[0],
1695
                    Category::OTHER_MISC,
1696
                    Category::MOVIE_OTHER,
1697
                    Category::TV_OTHER
1698
                ),
1699
                $rQuery->id
1700
            )
1701
        ) {
1702
            return false;
1703
        }
1704
1705
        if (File::isFile($fileLocation)) {
1706
1707
            // Check if media info is enabled.
1708
            if (! $retVal) {
1709
1710
                // Get the media info for the file.
1711
                $xmlArray = $this->mediaInfo->getInfo($fileLocation, false);
1712
1713
                if ($xmlArray !== null) {
1714
                    foreach ($xmlArray->getAudios() as $track) {
1715
                        if ($track->get('album') !== null && $track->get('performer') !== null) {
1716
                            if ((int) $this->_release->predb_id === 0 && config('nntmux.rename_music_mediainfo')) {
1717
                                // Make the extension upper case.
1718
                                $ext = strtoupper($fileExtension);
1719
1720
                                // Form a new search name.
1721
                                if (! empty($track->get('recorded_date')) && preg_match('/(?:19|20)\d\d/', $track->get('recorded_date')->getFullname(), $Year)) {
1722
                                    $newName = $track->get('performer')->getFullName().' - '.$track->get('album')->getFullName().' ('.$Year[0].') '.$ext;
1723
                                } else {
1724
                                    $newName = $track->get('performer')->getFullName().' - '.$track->get('album')->getFullName().' '.$ext;
1725
                                }
1726
1727
                                // Get the category or try to determine it.
1728
                                if ($ext === 'MP3') {
1729
                                    $newCat = Category::MUSIC_MP3;
1730
                                } elseif ($ext === 'FLAC') {
1731
                                    $newCat = Category::MUSIC_LOSSLESS;
1732
                                } else {
1733
                                    $newCat = $this->_categorize->determineCategory($rQuery->groups_id, $newName, $rQuery->fromname);
1734
                                }
1735
1736
                                $newTitle = escapeString(substr($newName, 0, 255));
1737
                                // Update the search name.
1738
                                DB::update(
1739
                                        sprintf(
1740
                                            '
1741
											UPDATE releases
1742
											SET searchname = %s, categories_id = %d, iscategorized = 1, isrenamed = 1, proc_pp = 1
1743
											WHERE id = %d',
1744
                                            $newTitle,
1745
                                            $newCat['categories_id'],
1746
                                            $this->_release->id
1747
                                        )
1748
                                    );
1749
                                $release = Release::find($this->_release->id);
1750
                                $release->retag($newCat['tags']);
1751
                                $this->sphinx->updateRelease($this->_release->id);
1752
1753
                                // Echo the changed name.
1754
                                if ($this->_echoCLI) {
1755
                                    NameFixer::echoChangedReleaseName(
1756
                                            [
1757
                                                'new_name' => $newName,
1758
                                                'old_name' => $rQuery->searchname,
1759
                                                'new_category' => $newCat,
1760
                                                'old_category' => $rQuery->id,
1761
                                                'group' => $rQuery->groups_id,
1762
                                                'releases_id' => $this->_release->id,
1763
                                                'method' => 'ProcessAdditional->_getAudioInfo',
1764
                                            ]
1765
                                        );
1766
                                }
1767
                            }
1768
1769
                            // Add the media info.
1770
                            $this->_releaseExtra->addFromXml($this->_release->id, $xmlArray);
1771
1772
                            $retVal = true;
1773
                            $this->_foundAudioInfo = true;
1774
                            if ($this->_echoCLI) {
1775
                                $this->_echo('a', 'primaryOver');
1776
                            }
1777
                            break;
1778
                        }
1779
                    }
1780
                }
1781
            }
1782
1783
            // Check if creating audio samples is enabled.
1784
            if (! $audVal) {
1785
1786
                // File name to store audio file.
1787
                $audioFileName = ($this->_release->guid.'.ogg');
1788
1789
                // Create an audio sample.
1790
                if ($this->ffprobe->isValid($fileLocation)) {
1791
                    try {
1792
                        $audioSample = $this->ffmpeg->open($fileLocation);
1793
                        $format = new Vorbis();
1794
                        $audioSample->clip(TimeCode::fromSeconds(30), TimeCode::fromSeconds(30));
0 ignored issues
show
Bug introduced by
The method clip() does not exist on FFMpeg\Media\Audio. It seems like you code against a sub-type of FFMpeg\Media\Audio such as FFMpeg\Media\Video. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1794
                        $audioSample->/** @scrutinizer ignore-call */ 
1795
                                      clip(TimeCode::fromSeconds(30), TimeCode::fromSeconds(30));
Loading history...
1795
                        $audioSample->save($format, $this->tmpPath.$audioFileName);
1796
                    } catch (\InvalidArgumentException $e) {
1797
                        if (config('app.debug') === true) {
1798
                            Log::error($e->getTraceAsString());
1799
                        }
1800
                        //We do nothing, just prevent displaying errors because the file cannot be open(corrupted or incomplete file)
1801
                    }
1802
                }
1803
1804
                // Check if the new file was created.
1805
                if (File::isFile($this->tmpPath.$audioFileName)) {
1806
1807
                    // Try to move the temp audio file.
1808
                    $renamed = File::move($this->tmpPath.$audioFileName, $this->_audioSavePath.$audioFileName);
1809
1810
                    if (! $renamed) {
1811
                        // Try to copy it if it fails.
1812
                        $copied = File::copy($this->tmpPath.$audioFileName, $this->_audioSavePath.$audioFileName);
1813
1814
                        // Delete the old file.
1815
                        File::delete($this->tmpPath.$audioFileName);
1816
1817
                        // If it didn't copy continue.
1818
                        if (! $copied) {
1819
                            return false;
1820
                        }
1821
                    }
1822
1823
                    // Try to set the file perms.
1824
                    @chmod($this->_audioSavePath.$audioFileName, 0764);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1824
                    /** @scrutinizer ignore-unhandled */ @chmod($this->_audioSavePath.$audioFileName, 0764);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1825
1826
                    // Update DB to said we got a audio sample.
1827
                    Release::query()->where('id', $this->_release->id)->update(['audiostatus' => 1]);
1828
1829
                    $audVal = $this->_foundAudioSample = true;
1830
1831
                    if ($this->_echoCLI) {
1832
                        $this->_echo('A', 'primaryOver');
1833
                    }
1834
                }
1835
            }
1836
        }
1837
1838
        return $retVal && $audVal;
1839
    }
1840
1841
    /**
1842
     * Try to get JPG picture, resize it and store it on disk.
1843
     *
1844
     * @param string $fileLocation
1845
     */
1846
    protected function _getJPGSample($fileLocation): void
1847
    {
1848
        // Try to resize/move the image.
1849
        $this->_foundJPGSample = (
1850
        $this->_releaseImage->saveImage(
1851
            $this->_release->guid.'_thumb',
1852
            $fileLocation,
1853
            $this->_releaseImage->jpgSavePath,
1854
            650,
1855
            650
1856
        ) === 1
1857
        );
1858
1859
        // If it's successful, tell the DB.
1860
        if ($this->_foundJPGSample) {
1861
            Release::query()->where('id', $this->_release->id)->update(['jpgstatus' => 1]);
1862
        }
1863
    }
1864
1865
    /**
1866
     * @param string $videoLocation
1867
     *
1868
     * @return string
1869
     */
1870
    private function getVideoTime($videoLocation): string
1871
    {
1872
        // Get the real duration of the file.
1873
        if ($this->ffprobe->isValid($videoLocation)) {
1874
            $time = $this->ffprobe->format($videoLocation)->get('duration');
1875
        }
1876
1877
        if (empty($time) || ! preg_match('/time=(\d{1,2}:\d{1,2}:)?(\d{1,2})\.(\d{1,2})\s*bitrate=/i', $time, $numbers)) {
1878
            return '';
1879
        }
1880
1881
        // Reduce the last number by 1, this is to make sure we don't ask avconv/ffmpeg for non existing data.
1882
        if ($numbers[3] > 0) {
1883
            $numbers[3] -= 1;
1884
        } elseif ($numbers[1] > 0) {
1885
            $numbers[2] -= 1;
1886
            $numbers[3] = '99';
1887
        }
1888
1889
        // Manually pad the numbers in case they are 1 number. to get 02 for example instead of 2.
1890
        return '00:00:'.str_pad($numbers[2], 2, '0', STR_PAD_LEFT).'.'.str_pad($numbers[3], 2, '0', STR_PAD_LEFT);
1891
    }
1892
1893
    /**
1894
     * @param string $fileLocation
1895
     * @return bool
1896
     * @throws \Exception
1897
     */
1898
    protected function _getSample(string $fileLocation): bool
1899
    {
1900
        if (! $this->_processThumbnails) {
1901
            return false;
1902
        }
1903
1904
        if (File::isFile($fileLocation)) {
1905
1906
            // Create path to temp file.
1907
            $fileName = ($this->tmpPath.'zzzz'.random_int(5, 12).random_int(5, 12).'.jpg');
1908
1909
            $time = $this->getVideoTime($fileLocation);
1910
1911
            // Create the image.
1912
            if ($this->ffprobe->isValid($fileLocation)) {
1913
                try {
1914
                    $this->ffmpeg->open($fileLocation)->frame(TimeCode::fromString($time === '' ? '00:00:03:00' : $time))->save($fileName);
0 ignored issues
show
Bug introduced by
The method frame() does not exist on FFMpeg\Media\Audio. It seems like you code against a sub-type of FFMpeg\Media\Audio such as FFMpeg\Media\Video. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1914
                    $this->ffmpeg->open($fileLocation)->/** @scrutinizer ignore-call */ frame(TimeCode::fromString($time === '' ? '00:00:03:00' : $time))->save($fileName);
Loading history...
1915
                } catch (\RuntimeException $runtimeException) {
1916
                    if (config('app.debug') === true) {
1917
                        Log::error($runtimeException->getTraceAsString());
1918
                    }
1919
                    //We show no error we just log it, we failed to save the frame and move on
1920
                } catch (\InvalidArgumentException $e) {
1921
                    if (config('app.debug') === true) {
1922
                        Log::error($e->getTraceAsString());
1923
                    }
1924
                    //We do nothing, just prevent displaying errors because the file cannot be open(corrupted or incomplete file)
1925
                } catch (\Throwable $e) {
1926
                    if (config('app.debug') === true) {
1927
                        Log::error($e->getTraceAsString());
1928
                    }
1929
                    //Again we do nothing, we just want to catch the error
1930
                }
1931
            }
1932
1933
            // Check if the file exists.
1934
            if (File::isFile($fileName)) {
1935
1936
                // Try to resize/move the image.
1937
                $saved = $this->_releaseImage->saveImage(
1938
                    $this->_release->guid.'_thumb',
1939
                    $fileName,
1940
                    $this->_releaseImage->imgSavePath,
1941
                    800,
1942
                    600
1943
                );
1944
1945
                // Delete the temp file we created.
1946
                File::delete($fileName);
1947
1948
                // Check if it saved.
1949
                if ($saved === 1) {
1950
                    if ($this->_echoCLI) {
1951
                        $this->_echo('s', 'primaryOver');
1952
                    }
1953
1954
                    return true;
1955
                }
1956
            }
1957
        }
1958
1959
        return false;
1960
    }
1961
1962
    /**
1963
     * @param string $fileLocation
1964
     * @return bool
1965
     * @throws \Exception
1966
     */
1967
    protected function _getVideo(string $fileLocation): bool
1968
    {
1969
        if (! $this->_processVideo) {
1970
            return false;
1971
        }
1972
1973
        // Try to find an avi file.
1974
        if (File::isFile($fileLocation)) {
1975
1976
            // Create a filename to store the temp file.
1977
            $fileName = ($this->tmpPath.'zzzz'.$this->_release->guid.'.ogv');
1978
1979
            $newMethod = false;
1980
            // If wanted sample length is less than 60, try to get sample from the end of the video.
1981
            if ($this->_ffMPEGDuration < 60) {
1982
                // Get the real duration of the file.
1983
                $time = $this->getVideoTime($fileLocation);
1984
1985
                if ($time !== '' && preg_match('/(\d{2}).(\d{2})/', $time, $numbers)) {
1986
                    $newMethod = true;
1987
                    // Get the lowest time we can start making the video at based on how many seconds the admin wants the video to be.
1988
                    if ($numbers[1] <= $this->_ffMPEGDuration) {
1989
                        // If the clip is shorter than the length we want.
1990
                        // The lowest we want is 0.
1991
                        $lowestLength = '00:00:00.00';
1992
                    } else {
1993
                        // If the clip is longer than the length we want.
1994
                        // The lowest we want is the the difference between the max video length and our wanted total time.
1995
                        $lowestLength = ($numbers[1] - $this->_ffMPEGDuration);
1996
                        // Form the time string.
1997
                        $end = '.'.$numbers[2];
1998
                        switch (\strlen($lowestLength)) {
1999
                            case 1:
2000
                                $lowestLength = ('00:00:0'.$lowestLength.$end);
2001
                                break;
2002
                            case 2:
2003
                                $lowestLength = ('00:00:'.$lowestLength.$end);
2004
                                break;
2005
                            default:
2006
                                $lowestLength = '00:00:60.00';
2007
                        }
2008
                    }
2009
2010
                    // Try to get the sample (from the end instead of the start).
2011
                    if ($this->ffprobe->isValid($fileLocation)) {
2012
                        try {
2013
                            $video = $this->ffmpeg->open($fileLocation);
2014
                            $videoSample = $video->clip(TimeCode::fromString($lowestLength), TimeCode::fromSeconds($this->_ffMPEGDuration));
2015
                            $format = new Ogg();
2016
                            $format->setAudioCodec(new Vorbis());
0 ignored issues
show
Bug introduced by
new FFMpeg\Format\Audio\Vorbis() of type FFMpeg\Format\Audio\Vorbis is incompatible with the type string expected by parameter $audioCodec of FFMpeg\Format\Audio\DefaultAudio::setAudioCodec(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2016
                            $format->setAudioCodec(/** @scrutinizer ignore-type */ new Vorbis());
Loading history...
2017
                            $videoSample->filters()->resize(new Dimension(320, -1), ResizeFilter::RESIZEMODE_SCALE_HEIGHT);
2018
                            $videoSample->save($format, $fileName);
2019
                        } catch (\InvalidArgumentException $e) {
2020
                            if (config('app.debug') === true) {
2021
                                Log::error($e->getTraceAsString());
2022
                            }
2023
                            //We do nothing, just prevent displaying errors because the file cannot be open(corrupted or incomplete file)
2024
                        }
2025
                    }
2026
                }
2027
            }
2028
2029
            if (! $newMethod) {
2030
                // If longer than 60 or we could not get the video length, run the old way.
2031
                if ($this->ffprobe->isValid($fileLocation)) {
2032
                    try {
2033
                        $video = $this->ffmpeg->open($fileLocation);
2034
                        $videoSample = $video->clip(TimeCode::fromSeconds(0), TimeCode::fromSeconds($this->_ffMPEGDuration));
2035
                        $format = new Ogg();
2036
                        $format->setAudioCodec(new Vorbis());
2037
                        $videoSample->filters()->resize(new Dimension(320, -1), ResizeFilter::RESIZEMODE_SCALE_HEIGHT);
2038
                        $videoSample->save($format, $fileName);
2039
                    } catch (\InvalidArgumentException $e) {
2040
                        if (config('app.debug') === true) {
2041
                            Log::error($e->getTraceAsString());
2042
                        }
2043
                        //We do nothing, just prevent displaying errors because the file cannot be open(corrupted or incomplete file)
2044
                    }
2045
                }
2046
            }
2047
2048
            // Until we find the video file.
2049
            if (File::isFile($fileName)) {
2050
2051
                // Create a path to where the file should be moved.
2052
                $newFile = ($this->_releaseImage->vidSavePath.$this->_release->guid.'.ogv');
2053
2054
                // Try to move the file to the new path.
2055
                $renamed = @File::move($fileName, $newFile);
2056
2057
                // If we couldn't rename it, try to copy it.
2058
                if (! $renamed) {
2059
                    $copied = @File::copy($fileName, $newFile);
2060
2061
                    // Delete the old file.
2062
                    File::delete($fileName);
2063
2064
                    // If it didn't copy, continue.
2065
                    if (! $copied) {
2066
                        return false;
2067
                    }
2068
                }
2069
2070
                // Change the permissions.
2071
                @chmod($newFile, 0764);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

2071
                /** @scrutinizer ignore-unhandled */ @chmod($newFile, 0764);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2072
2073
                // Update query to say we got the video.
2074
                Release::query()->where('guid', $this->_release->guid)->update(['videostatus' => 1]);
2075
                if ($this->_echoCLI) {
2076
                    $this->_echo('v', 'primaryOver');
2077
                }
2078
2079
                return true;
2080
            }
2081
        }
2082
2083
        return false;
2084
    }
2085
2086
    /**
2087
     * @param $fileLocation
2088
     * @return bool
2089
     * @throws \Exception
2090
     */
2091
    protected function _getMediaInfo($fileLocation): bool
2092
    {
2093
        if (! $this->_processMediaInfo) {
2094
            return false;
2095
        }
2096
2097
        // Look for the video file.
2098
        if (File::isFile($fileLocation)) {
2099
            $xmlArray = $this->mediaInfo->getInfo($fileLocation, false);
2100
2101
            // Check if we got it.
2102
2103
            if ($xmlArray === null) {
2104
                return false;
2105
            }
2106
2107
            // Insert it into the DB.
2108
            $this->_releaseExtra->addFull($this->_release->id, $xmlArray);
2109
            $this->_releaseExtra->addFromXml($this->_release->id, $xmlArray);
2110
2111
            if ($this->_echoCLI) {
2112
                $this->_echo('m', 'primaryOver');
2113
            }
2114
2115
            return true;
2116
        }
2117
2118
        return false;
2119
    }
2120
2121
    /**
2122
     * @param $fileLocation
2123
     * @throws \Exception
2124
     */
2125
    protected function _siftPAR2Info($fileLocation): void
2126
    {
2127
        $this->_par2Info->open($fileLocation);
2128
2129
        if ($this->_par2Info->error) {
2130
            return;
2131
        }
2132
2133
        $releaseInfo = DB::selectOne(
2134
            sprintf(
2135
                '
2136
				SELECT UNIX_TIMESTAMP(postdate) AS postdate, proc_pp
2137
				FROM releases
2138
				WHERE id = %d',
2139
                $this->_release->id
2140
            )
2141
        );
2142
2143
        if ($releaseInfo === null) {
2144
            return;
2145
        }
2146
2147
        // Only get a new name if the category is OTHER.
2148
        $foundName = true;
2149
        if ((int) $releaseInfo->proc_pp === 0 && config('nntmux.rename_par2') &&
2150
            \in_array(
2151
                (int) $this->_release->categories_id,
2152
                Category::OTHERS_GROUP,
2153
                false
2154
            )
2155
        ) {
2156
            $foundName = false;
2157
        }
2158
2159
        $filesAdded = 0;
2160
2161
        $files = $this->_par2Info->getFileList();
2162
        foreach ($files as $file) {
2163
            if (! isset($file['name'])) {
2164
                continue;
2165
            }
2166
2167
            // If we found a name and added 10 files, stop.
2168
            if ($foundName && $filesAdded > 10) {
2169
                break;
2170
            }
2171
2172
            // Add to release files.
2173
            if ($this->_addPAR2Files) {
2174
                if ($filesAdded < 11 && ReleaseFile::query()->where(['releases_id' => $this->_release->id, 'name' => $file['name']])->first() === null
2175
                ) {
2176
2177
                    // Try to add the files to the DB.
2178
                    if (ReleaseFile::addReleaseFiles($this->_release->id, $file['name'], $file['size'], $releaseInfo->postdate, 0, $file['hash_16K'])) {
2179
                        $filesAdded++;
2180
                    }
2181
                }
2182
            } else {
2183
                $filesAdded++;
2184
            }
2185
2186
            // Try to get a new name.
2187
            if (! $foundName) {
2188
                $this->_release->textstring = $file['name'];
2189
                $this->_release->releases_id = $this->_release->id;
2190
                if ($this->_nameFixer->checkName($this->_release, ($this->_echoCLI ? true : false), 'PAR2, ', 1, 1)) {
2191
                    $foundName = true;
2192
                }
2193
            }
2194
        }
2195
        // Update the file count with the new file count + old file count.
2196
        Release::query()->where('id', $this->_release->id)->increment('rarinnerfilecount', $filesAdded);
2197
        $this->_foundPAR2Info = true;
2198
    }
2199
2200
    /**
2201
     * @param $fileLocation
2202
     * @throws \Exception
2203
     */
2204
    protected function _processNfoFile($fileLocation): void
2205
    {
2206
        $data = @File::get($fileLocation);
2207
        if ($data !== false && $this->_nfo->isNFO($data, $this->_release->guid) && $this->_nfo->addAlternateNfo($data, (array) $this->_release, $this->_nntp)) {
2208
            $this->_releaseHasNoNFO = false;
2209
        }
2210
    }
2211
2212
    /**
2213
     * @param $fileLocation
2214
     * @throws \Exception
2215
     */
2216
    protected function _processVideoFile($fileLocation): void
2217
    {
2218
        // Try to get a sample with it.
2219
        if (! $this->_foundSample) {
2220
            $this->_foundSample = $this->_getSample($fileLocation);
2221
        }
2222
2223
        /* Try to get a video with it.
2224
         * Don't get it here if _sampleMessageIDs is empty
2225
         * or has 1 message-id (Saves downloading another part).
2226
         */
2227
        if (! $this->_foundVideo && \count($this->_sampleMessageIDs) < 2) {
2228
            $this->_foundVideo = $this->_getVideo($fileLocation);
2229
        }
2230
2231
        // Try to get media info with it.
2232
        if (! $this->_foundMediaInfo) {
2233
            $this->_foundMediaInfo = $this->_getMediaInfo($fileLocation);
2234
        }
2235
    }
2236
2237
    /**
2238
     * Comparison function for uSort, for sorting NZB files.
2239
     *
2240
     * @param array|null|string $a
2241
     * @param array|null|string $b
2242
     *
2243
     * @return int
2244
     */
2245
    protected function _sortNZB($a, $b): int
2246
    {
2247
        $pos = 0;
2248
        $af = $bf = false;
2249
        $a = preg_replace('/\d+[- ._]?(\/|\||[o0]f)[- ._]?\d+?(?![- ._]\d)/i', ' ', $a['title']);
2250
        $b = preg_replace('/\d+[- ._]?(\/|\||[o0]f)[- ._]?\d+?(?![- ._]\d)/i', ' ', $b['title']);
2251
2252
        if (preg_match('/\.(part\d+|r\d+)(\s*\.rar)*($|[ ")\]-])/i', $a)) {
2253
            $af = true;
2254
        }
2255
        if (preg_match('/\.(part\d+|r\d+)(\s*\.rar)*($|[ ")\]-])/i', $b)) {
2256
            $bf = true;
2257
        }
2258
2259
        if (! $af && preg_match('/\.rar($|[ ")\]-])/i', $a)) {
2260
            $a = preg_replace('/\.rar(?:$|[ ")\]-])/i', '.*rar', $a);
2261
            $af = true;
2262
        }
2263
        if (! $bf && preg_match('/\.rar($|[ ")\]-])/i', $b)) {
2264
            $b = preg_replace('/\.rar(?:$|[ ")\]-])/i', '.*rar', $b);
2265
            $bf = true;
2266
        }
2267
2268
        if (! $af && ! $bf) {
2269
            return strnatcasecmp($a, $b);
2270
        }
2271
2272
        if (! $bf) {
2273
            return -1;
2274
        }
2275
2276
        if (! $af) {
2277
            return 1;
2278
        }
2279
2280
        if ($af && $bf) {
0 ignored issues
show
introduced by
The condition $bf is always true.
Loading history...
2281
            return strnatcasecmp($a, $b);
2282
        }
2283
2284
        if ($af) {
2285
            return -1;
2286
        }
2287
2288
        if ($bf) {
2289
            return 1;
2290
        }
2291
2292
        return $pos;
2293
    }
2294
2295
    /**
2296
     * Reset some variables for the current release.
2297
     */
2298
    protected function _resetReleaseStatus(): void
2299
    {
2300
        // Only process for samples, previews and images if not disabled.
2301
        $this->_foundVideo = $this->_processVideo ? false : true;
2302
        $this->_foundMediaInfo = $this->_processMediaInfo ? false : true;
2303
        $this->_foundAudioInfo = $this->_processAudioInfo ? false : true;
2304
        $this->_foundAudioSample = $this->_processAudioSample ? false : true;
2305
        $this->_foundJPGSample = $this->_processJPGSample ? false : true;
2306
        $this->_foundSample = $this->_processThumbnails ? false : true;
2307
        $this->_foundPAR2Info = false;
2308
2309
        $this->_passwordStatus = [Releases::PASSWD_NONE];
2310
        $this->_releaseHasPassword = false;
2311
2312
        $this->_releaseGroupName = Group::getNameByID($this->_release->groups_id);
2313
2314
        $this->_releaseHasNoNFO = false;
2315
        // Make sure we don't already have an nfo.
2316
        if ((int) $this->_release->nfostatus !== 1) {
2317
            $this->_releaseHasNoNFO = true;
2318
        }
2319
2320
        $this->_NZBHasCompressedFile = false;
2321
2322
        $this->_sampleMessageIDs = $this->_JPGMessageIDs = $this->_MediaInfoMessageIDs = [];
2323
        $this->_AudioInfoMessageIDs = $this->_RARFileMessageIDs = [];
2324
        $this->_AudioInfoExtension = '';
2325
2326
        $this->_addedFileInfo = 0;
2327
        $this->_totalFileInfo = 0;
2328
        $this->_compressedFilesChecked = 0;
2329
    }
2330
2331
    /**
2332
     * Echo a string to CLI.
2333
     *
2334
     * @param string $string  String to echo.
2335
     * @param string $type    Method type.
2336
     *
2337
     * @void
2338
     */
2339
    protected function _echo($string, $type): void
2340
    {
2341
        if ($this->_echoCLI) {
2342
            (new ColorCLI())->$type($string);
2343
        }
2344
    }
2345
2346
    /**
2347
     * Echo a string to CLI. For debugging.
2348
     *
2349
     * @param string $string
2350
     *
2351
     * @void
2352
     */
2353
    protected function _debug($string): void
2354
    {
2355
        $this->_echo('DEBUG: '.$string, 'debug');
2356
    }
2357
}
2358