Completed
Push — dev ( 96fe6c...04e8b1 )
by Darko
08:41
created

ProcessAdditional::_getTempDirectoryContents()   B

Complexity

Conditions 7
Paths 12

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 27
ccs 0
cts 13
cp 0
rs 8.8333
c 0
b 0
f 0
cc 7
nc 12
nop 2
crap 56
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
            $this->_debug('Data is probably not RAR or ZIP.');
1049
1050
            return false;
1051
        }
1052
1053
        // Check if there's an error.
1054
        if ($this->_archiveInfo->error !== '') {
1055
            $this->_debug('ArchiveInfo Error: '.$this->_archiveInfo->error);
1056
1057
            return false;
1058
        }
1059
1060
        // Get a summary of the compressed file.
1061
        $dataSummary = $this->_archiveInfo->getSummary(true);
1062
1063
        // Check if the compressed file is encrypted.
1064
        if (! empty($this->_archiveInfo->isEncrypted) || (isset($dataSummary['is_encrypted']) && (int) $dataSummary['is_encrypted'] !== 0)) {
1065
            $this->_debug('ArchiveInfo: Compressed file has a password.');
1066
            $this->_releaseHasPassword = true;
1067
            $this->_passwordStatus[] = Releases::PASSWD_RAR;
1068
1069
            return false;
1070
        }
1071
1072
        switch ($dataSummary['main_type']) {
1073
            case ArchiveInfo::TYPE_RAR:
1074
                if ($this->_echoCLI) {
1075
                    $this->_echo('r', 'primaryOver');
1076
                }
1077
1078
                if (! $this->_extractUsingRarInfo && $this->_unrarPath !== false) {
1079
                    $fileName = $this->tmpPath.uniqid('', true).'.rar';
1080
                    File::put($fileName, $compressedData);
1081
                    runCmd(
1082
                        $this->_killString.$this->_unrarPath.
1083
                        '" e -ai -ep -c- -id -inul -kb -or -p- -r -y "'.
1084
                        $fileName.'" "'.$this->tmpPath.'unrar/"'
1085
                    );
1086
                    File::delete($fileName);
1087
                }
1088
                break;
1089
            case ArchiveInfo::TYPE_ZIP:
1090
                if ($this->_echoCLI) {
1091
                    $this->_echo('z', 'primaryOver');
1092
                }
1093
1094
                if (! $this->_extractUsingRarInfo && $this->_7zipPath !== false) {
1095
                    $fileName = $this->tmpPath.uniqid('', true).'.zip';
1096
                    File::put($fileName, $compressedData);
1097
                    runCmd(
1098
                        $this->_killString.$this->_7zipPath.'" x "'.
1099
                        $fileName.'" -bd -y -o"'.$this->tmpPath.'unzip/"'
1100
                    );
1101
                    File::delete($fileName);
1102
                }
1103
                break;
1104
            default:
1105
                return false;
1106
        }
1107
1108
        return $this->_processCompressedFileList();
1109
    }
1110
1111
    /**
1112
     * Get a list of all files in the compressed file, add the file info to the DB.
1113
     *
1114
     * @return bool
1115
     * @throws \Exception
1116
     */
1117
    protected function _processCompressedFileList(): bool
1118
    {
1119
        // Get a list of files inside the Compressed file.
1120
        $files = $this->_archiveInfo->getArchiveFileList();
1121
        if (! \is_array($files) || \count($files) === 0) {
0 ignored issues
show
introduced by
The condition is_array($files) is always true.
Loading history...
1122
            return false;
1123
        }
1124
1125
        // Loop through the files.
1126
        foreach ($files as $file) {
1127
            if ($this->_releaseHasPassword) {
1128
                break;
1129
            }
1130
1131
            if (isset($file['name'])) {
1132
                if (isset($file['error'])) {
1133
                    $this->_debug("Error: {$file['error']} (in: {$file['source']})");
1134
                    continue;
1135
                }
1136
1137
                if (isset($file['pass']) && $file['pass'] === true) {
1138
                    $this->_releaseHasPassword = true;
1139
                    $this->_passwordStatus[] = Releases::PASSWD_RAR;
1140
                    break;
1141
                }
1142
1143
                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

1143
                if ($this->_innerFileBlacklist !== false && preg_match(/** @scrutinizer ignore-type */ $this->_innerFileBlacklist, $file['name'])) {
Loading history...
1144
                    $this->_releaseHasPassword = true;
1145
                    $this->_passwordStatus[] = Releases::PASSWD_POTENTIAL;
1146
                    break;
1147
                }
1148
1149
                $fileName = [];
1150
                if (preg_match('/[^\/\\\\]*\.[a-zA-Z0-9]*$/', $file['name'], $fileName)) {
1151
                    $fileName = $fileName[0];
1152
                } else {
1153
                    $fileName = '';
1154
                }
1155
1156
                if ($this->_extractUsingRarInfo) {
1157
                    // Extract files from the rar.
1158
                    if (isset($file['compressed']) && (int) $file['compressed'] === 0) {
1159
                        File::put(
1160
                            $this->tmpPath.random_int(10, 999999).'_'.$fileName,
1161
                            $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

1161
                            /** @scrutinizer ignore-type */ $this->_archiveInfo->getFileData($file['name'], $file['source'])
Loading history...
1162
                        );
1163
                    } // If the files are compressed, use a binary extractor.
1164
                    else {
1165
                        $this->_archiveInfo->extractFile($file['name'], $this->tmpPath.random_int(10, 999999).'_'.$fileName);
1166
                    }
1167
                }
1168
            }
1169
            $this->_addFileInfo($file);
1170
        }
1171
        if ($this->_addedFileInfo > 0) {
1172
            $this->sphinx->updateRelease($this->_release->id);
1173
        }
1174
1175
        return $this->_totalFileInfo > 0;
1176
    }
1177
1178
    /**
1179
     * @param $file
1180
     * @throws \Exception
1181
     */
1182
    protected function _addFileInfo(&$file): void
1183
    {
1184
        // Don't add rar/zip files to the DB.
1185
        if (! isset($file['error']) && isset($file['source']) &&
1186
            ! 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'])
1187
        ) {
1188
1189
            // Cache the amount of files we find in the RAR or ZIP, return this to say we did find RAR or ZIP content.
1190
            // This is so we don't download more RAR or ZIP files for no reason.
1191
            $this->_totalFileInfo++;
1192
1193
            /* Check if we already have the file or not.
1194
             * Also make sure we don't add too many files, some releases have 100's of files, like PS3 releases.
1195
             */
1196
            if ($this->_addedFileInfo < 11 && ReleaseFile::query()->where(['releases_id' => $this->_release->id, 'name' => $file['name'], 'size' => $file
1197
                ['size'], ])->first() === null) {
1198
                if (ReleaseFile::addReleaseFiles($this->_release->id, $file['name'], $file['size'], $file['date'], $file['pass'], '', $file['crc32'] ?? '')) {
1199
                    $this->_addedFileInfo++;
1200
1201
                    if ($this->_echoCLI) {
1202
                        $this->_echo('^', 'primaryOver');
1203
                    }
1204
1205
                    // Check for "codec spam"
1206
                    if (preg_match('/alt\.binaries\.movies($|\.divx$)/', $this->_releaseGroupName) &&
1207
                        preg_match('/[\/\\\\]Codec[\/\\\\]Setup\.exe$/i', $file['name'])
1208
                    ) {
1209
                        $this->_debug('Codec spam found, setting release to potentially passworded.');
1210
                        $this->_releaseHasPassword = true;
1211
                        $this->_passwordStatus[] = Releases::PASSWD_POTENTIAL;
1212
                    } //Run a PreDB filename check on insert to try and match the release
1213
                    elseif ($file['name'] !== '' && strpos($file['name'], '.') !== 0) {
1214
                        $this->_release['filename'] = $file['name'];
1215
                        $this->_release['releases_id'] = $this->_release->id;
1216
                        $this->_nameFixer->matchPreDbFiles($this->_release, 1, 1, true);
1217
                    }
1218
                }
1219
            }
1220
        }
1221
    }
1222
1223
    /**
1224
     * Go through all the extracted files in the temp folder and process them.
1225
     *
1226
     * @throws \Exception
1227
     */
1228
    protected function _processExtractedFiles(): void
1229
    {
1230
        $nestedLevels = 0;
1231
1232
        // Go through all the files in the temp folder, look for compressed files, extract them and the nested ones.
1233
        while ($nestedLevels < $this->_maxNestedLevels) {
1234
1235
            // Break out if we checked more than x compressed files.
1236
            if ($this->_compressedFilesChecked >= self::maxCompressedFilesToCheck) {
1237
                break;
1238
            }
1239
1240
            $foundCompressedFile = false;
1241
1242
            // Get all the compressed files in the temp folder.
1243
            $files = $this->_getTempDirectoryContents('/.*\.([rz]\d{2,}|rar|zipx?|0{0,2}1)($|[^a-z0-9])/i');
1244
1245
            if ($files instanceof \Traversable) {
1246
                foreach ($files as $file) {
1247
1248
                    // Check if the file exists.
1249
                    if (File::isFile($file[0])) {
1250
                        $rarData = @File::get($file[0]);
1251
                        if ($rarData !== false) {
1252
                            $this->_processCompressedData($rarData);
1253
                            $foundCompressedFile = true;
1254
                        }
1255
                        File::delete($file[0]);
1256
                    }
1257
                }
1258
            }
1259
1260
            // If we found no compressed files, break out.
1261
            if (! $foundCompressedFile) {
1262
                break;
1263
            }
1264
1265
            $nestedLevels++;
1266
        }
1267
1268
        $fileType = [];
1269
1270
        // Get all the remaining files in the temp dir.
1271
        $files = $this->_getTempDirectoryContents();
1272
        foreach ($files as $file) {
1273
            $file = $file->getPathname();
1274
1275
            // Skip /. and /..
1276
            if (preg_match('/[\/\\\\]\.{1,2}$/', $file)) {
1277
                continue;
1278
            }
1279
1280
            if (File::isFile($file)) {
1281
1282
                    // Process PAR2 files.
1283
                if (! $this->_foundPAR2Info && preg_match('/\.par2$/', $file)) {
1284
                    $this->_siftPAR2Info($file);
1285
                } // Process NFO files.
1286
                elseif ($this->_releaseHasNoNFO && preg_match('/(\.(nfo|inf|ofn)|info\.txt)$/i', $file)) {
1287
                    $this->_processNfoFile($file);
1288
                } // Process audio files.
1289
                elseif (
1290
                        (! $this->_foundAudioInfo || ! $this->_foundAudioSample) &&
1291
                        preg_match('/(.*)'.$this->_audioFileRegex.'$/i', $file, $fileType)
1292
                    ) {
1293
                    // Try to get audio sample/audio media info.
1294
                    File::move($file, $this->tmpPath.'audiofile.'.$fileType[2]);
1295
                    $this->_getAudioInfo($this->tmpPath.'audiofile.'.$fileType[2], $fileType[2]);
1296
                    File::delete($this->tmpPath.'audiofile.'.$fileType[2]);
1297
                } // Process JPG files.
1298
                elseif (! $this->_foundJPGSample && preg_match('/\.jpe?g$/i', $file)) {
1299
                    $this->_getJPGSample($file);
1300
                    File::delete($file);
1301
                } // Video sample // video clip // video media info.
1302
                elseif ((! $this->_foundSample || ! $this->_foundVideo || ! $this->_foundMediaInfo) &&
1303
                        preg_match('/(.*)'.$this->_videoFileRegex.'$/i', $file)
1304
                    ) {
1305
                    $this->_processVideoFile($file);
1306
                }
1307
1308
                // Check file's magic info.
1309
                else {
1310
                    $output = Utility::fileInfo($file);
1311
                    if (! empty($output)) {
1312
                        switch (true) {
1313
1314
                                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...
1315
                                    $this->_getJPGSample($file);
1316
                                    File::delete($file);
1317
                                    break;
1318
1319
                                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...
1320
                                    (! $this->_foundMediaInfo || ! $this->_foundSample || ! $this->_foundVideo)
1321
                                    && preg_match('/Matroska data|MPEG v4|MPEG sequence, v2|\WAVI\W/i', $output):
1322
                                    $this->_processVideoFile($file);
1323
                                    break;
1324
1325
                                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...
1326
                                    (! $this->_foundAudioSample || ! $this->_foundAudioInfo) &&
1327
                                    preg_match('/^FLAC|layer III|Vorbis audio/i', $output, $fileType):
1328
                                    switch ($fileType[0]) {
1329
                                        case 'FLAC':
1330
                                            $fileType = 'FLAC';
1331
                                            break;
1332
                                        case 'layer III':
1333
                                            $fileType = 'MP3';
1334
                                            break;
1335
                                        case 'Vorbis audio':
1336
                                            $fileType = 'OGG';
1337
                                            break;
1338
                                    }
1339
                                    File::move($file, $this->tmpPath.'audiofile.'.$fileType);
1340
                                    $this->_getAudioInfo($this->tmpPath.'audiofile.'.$fileType, $fileType);
1341
                                    File::delete($this->tmpPath.'audiofile.'.$fileType);
1342
                                    break;
1343
1344
                                case ! $this->_foundPAR2Info && stripos($output, 'Parity') === 0:
1345
                                    $this->_siftPAR2Info($file);
1346
                                    break;
1347
                            }
1348
                    }
1349
                }
1350
            }
1351
        }
1352
    }
1353
1354
    /**
1355
     * Download all binaries from usenet and form samples / get media info / etc from them.
1356
     *
1357
     * @void
1358
     * @throws \Exception
1359
     */
1360
    protected function _processMessageIDDownloads(): void
1361
    {
1362
        $this->_processSampleMessageIDs();
1363
        $this->_processMediaInfoMessageIDs();
1364
        $this->_processAudioInfoMessageIDs();
1365
        $this->_processJPGMessageIDs();
1366
    }
1367
1368
    /**
1369
     * Download and process binaries for sample videos.
1370
     *
1371
     * @void
1372
     * @throws \Exception
1373
     */
1374
    protected function _processSampleMessageIDs(): void
1375
    {
1376
        // Download and process sample image.
1377
        if (! $this->_foundSample || ! $this->_foundVideo) {
1378
            if (! empty($this->_sampleMessageIDs)) {
1379
1380
                // Download it from usenet.
1381
                $sampleBinary = $this->_nntp->getMessages($this->_releaseGroupName, $this->_sampleMessageIDs, $this->_alternateNNTP);
1382
                if ($this->_nntp->isError($sampleBinary)) {
1383
                    $sampleBinary = false;
1384
                }
1385
1386
                if ($sampleBinary !== false) {
1387
                    if ($this->_echoCLI) {
1388
                        $this->_echo('(sB)', 'primaryOver');
1389
                    }
1390
1391
                    // Check if it's more than 40 bytes.
1392
                    if (\strlen($sampleBinary) > 40) {
1393
                        $fileLocation = $this->tmpPath.'sample_'.random_int(0, 99999).'.avi';
1394
                        // Try to create the file.
1395
                        File::put($fileLocation, $sampleBinary);
1396
1397
                        // Try to get a sample picture.
1398
                        if (! $this->_foundSample) {
1399
                            $this->_foundSample = $this->_getSample($fileLocation);
1400
                        }
1401
1402
                        // Try to get a sample video.
1403
                        if (! $this->_foundVideo) {
1404
                            $this->_foundVideo = $this->_getVideo($fileLocation);
1405
                        }
1406
                    }
1407
                } elseif ($this->_echoCLI) {
1408
                    $this->_echo('f', 'warningOver');
1409
                }
1410
            }
1411
        }
1412
    }
1413
1414
    /**
1415
     * Download and process binaries for media info from videos.
1416
     *
1417
     * @void
1418
     * @throws \Exception
1419
     */
1420
    protected function _processMediaInfoMessageIDs(): void
1421
    {
1422
        // Download and process mediainfo. Also try to get a sample if we didn't get one yet.
1423
        if (! $this->_foundMediaInfo || ! $this->_foundSample || ! $this->_foundVideo) {
1424
            if (! $this->_foundMediaInfo && ! empty($this->_MediaInfoMessageIDs)) {
1425
1426
                // Try to download it from usenet.
1427
                $mediaBinary = $this->_nntp->getMessages($this->_releaseGroupName, $this->_MediaInfoMessageIDs, $this->_alternateNNTP);
1428
                if ($this->_nntp->isError($mediaBinary)) {
1429
                    // If error set it to false.
1430
                    $mediaBinary = false;
1431
                }
1432
1433
                if ($mediaBinary !== false) {
1434
                    if ($this->_echoCLI) {
1435
                        $this->_echo('(mB)', 'primaryOver');
1436
                    }
1437
1438
                    // If it's more than 40 bytes...
1439
                    if (\strlen($mediaBinary) > 40) {
1440
                        $fileLocation = $this->tmpPath.'media.avi';
1441
                        // Create a file on the disk with it.
1442
                        File::put($fileLocation, $mediaBinary);
1443
1444
                        // Try to get media info.
1445
                        if (! $this->_foundMediaInfo) {
1446
                            $this->_foundMediaInfo = $this->_getMediaInfo($fileLocation);
1447
                        }
1448
1449
                        // Try to get a sample picture.
1450
                        if (! $this->_foundSample) {
1451
                            $this->_foundSample = $this->_getSample($fileLocation);
1452
                        }
1453
1454
                        // Try to get a sample video.
1455
                        if (! $this->_foundVideo) {
1456
                            $this->_foundVideo = $this->_getVideo($fileLocation);
1457
                        }
1458
                    }
1459
                } elseif ($this->_echoCLI) {
1460
                    $this->_echo('f', 'warningOver');
1461
                }
1462
            }
1463
        }
1464
    }
1465
1466
    /**
1467
     * Download and process binaries for media info from songs.
1468
     *
1469
     * @void
1470
     * @throws \Exception
1471
     */
1472
    protected function _processAudioInfoMessageIDs(): void
1473
    {
1474
        // Download audio file, use media info to try to get the artist / album.
1475
        if (! $this->_foundAudioInfo || ! $this->_foundAudioSample) {
1476
            if (! empty($this->_AudioInfoMessageIDs)) {
1477
                // Try to download it from usenet.
1478
                $audioBinary = $this->_nntp->getMessages($this->_releaseGroupName, $this->_AudioInfoMessageIDs, $this->_alternateNNTP);
1479
                if ($this->_nntp->isError($audioBinary)) {
1480
                    $audioBinary = false;
1481
                }
1482
1483
                if ($audioBinary !== false) {
1484
                    if ($this->_echoCLI) {
1485
                        $this->_echo('(aB)', 'primaryOver');
1486
                    }
1487
1488
                    $fileLocation = $this->tmpPath.'audio.'.$this->_AudioInfoExtension;
1489
                    // Create a file with it.
1490
                    File::put($fileLocation, $audioBinary);
1491
1492
                    // Try to get media info / sample of the audio file.
1493
                    $this->_getAudioInfo($fileLocation, $this->_AudioInfoExtension);
1494
                } elseif ($this->_echoCLI) {
1495
                    $this->_echo('f', 'warningOver');
1496
                }
1497
            }
1498
        }
1499
    }
1500
1501
    /**
1502
     * Download and process binaries for JPG pictures.
1503
     *
1504
     * @void
1505
     * @throws \Exception
1506
     */
1507
    protected function _processJPGMessageIDs(): void
1508
    {
1509
        // Download JPG file.
1510
        if (! $this->_foundJPGSample && ! empty($this->_JPGMessageIDs)) {
1511
1512
            // Try to download it.
1513
            $jpgBinary = $this->_nntp->getMessages($this->_releaseGroupName, $this->_JPGMessageIDs, $this->_alternateNNTP);
1514
            if ($this->_nntp->isError($jpgBinary)) {
1515
                $jpgBinary = false;
1516
            }
1517
1518
            if ($jpgBinary !== false) {
1519
                if ($this->_echoCLI) {
1520
                    $this->_echo('(jB)', 'primaryOver');
1521
                }
1522
1523
                // Try to create a file with it.
1524
                File::put($this->tmpPath.'samplepicture.jpg', $jpgBinary);
1525
1526
                // Try to resize and move it.
1527
                $this->_foundJPGSample = (
1528
                $this->_releaseImage->saveImage(
1529
                    $this->_release->guid.'_thumb',
1530
                    $this->tmpPath.'samplepicture.jpg',
1531
                    $this->_releaseImage->jpgSavePath,
1532
                    650,
1533
                    650
1534
                ) === 1
1535
                );
1536
1537
                if ($this->_foundJPGSample) {
1538
                    // Update the DB to say we got it.
1539
                    Release::query()->where('id', $this->_release->id)->update(['jpgstatus' => 1]);
1540
1541
                    if ($this->_echoCLI) {
1542
                        $this->_echo('j', 'primaryOver');
1543
                    }
1544
                }
1545
1546
                File::delete($this->tmpPath.'samplepicture.jpg');
1547
            } elseif ($this->_echoCLI) {
1548
                $this->_echo('f', 'warningOver');
1549
            }
1550
        }
1551
    }
1552
1553
    /**
1554
     * Update the release to say we processed it.
1555
     */
1556
    protected function _finalizeRelease(): void
1557
    {
1558
        $vSQL = $jSQL = '';
1559
        $iSQL = ', haspreview = 0';
1560
1561
        // If samples exist from previous runs, set flags.
1562
        if (File::isFile($this->_releaseImage->imgSavePath.$this->_release->guid.'_thumb.jpg')) {
1563
            $iSQL = ', haspreview = 1';
1564
        }
1565
1566
        if (File::isFile($this->_releaseImage->vidSavePath.$this->_release->guid.'.ogv')) {
1567
            $vSQL = ', videostatus = 1';
1568
        }
1569
1570
        if (File::isFile($this->_releaseImage->jpgSavePath.$this->_release->guid.'_thumb.jpg')) {
1571
            $jSQL = ', jpgstatus = 1';
1572
        }
1573
1574
        // Get the amount of files we found inside the RAR/ZIP files.
1575
1576
        $releaseFilesCount = ReleaseFile::whereReleasesId($this->_release->id)->count('releases_id');
1577
1578
        if ($releaseFilesCount === null) {
1579
            $releaseFilesCount = 0;
1580
        }
1581
1582
        $this->_passwordStatus = max($this->_passwordStatus);
1583
1584
        // Set the release to no password if password processing is off.
1585
        if (! $this->_processPasswords) {
1586
            $this->_releaseHasPassword = false;
1587
        }
1588
1589
        // If we failed to get anything from the RAR/ZIPs, decrement the passwordstatus, if the rar/zip has no password.
1590
        if (! $this->_releaseHasPassword && $this->_NZBHasCompressedFile && $releaseFilesCount === 0) {
1591
            $query = sprintf(
1592
                'UPDATE releases
1593
				SET passwordstatus = passwordstatus - 1, rarinnerfilecount = %d %s %s %s
1594
				WHERE id = %d',
1595
                $releaseFilesCount,
1596
                $iSQL,
1597
                $vSQL,
1598
                $jSQL,
1599
                $this->_release->id
1600
            );
1601
        } // Else update the release with the password status (if the admin enabled the setting).
1602
        else {
1603
            $query = sprintf(
1604
                'UPDATE releases
1605
				SET passwordstatus = %d, rarinnerfilecount = %d %s %s %s
1606
				WHERE id = %d',
1607
                ($this->_processPasswords ? $this->_passwordStatus : Releases::PASSWD_NONE),
1608
                $releaseFilesCount,
1609
                $iSQL,
1610
                $vSQL,
1611
                $jSQL,
1612
                $this->_release->id
1613
            );
1614
        }
1615
1616
        Release::fromQuery($query);
1617
    }
1618
1619
    /**
1620
     * @param string $pattern
1621
     * @param string $path
1622
     *
1623
     * @return bool|string|\Symfony\Component\Finder\SplFileInfo[]
1624
     */
1625
    protected function _getTempDirectoryContents($pattern = '', $path = '')
1626
    {
1627
        if ($path === '') {
1628
            $path = $this->tmpPath;
1629
        }
1630
1631
        $files = File::allFiles($path);
1632
        try {
1633
            if ($pattern !== '') {
1634
                $allFiles = [];
1635
                foreach ($files as $file) {
1636
                    if (preg_match($pattern, $file->getRelativePathname())) {
1637
                        $allFiles .= $file;
1638
                    }
1639
                }
1640
1641
                return $allFiles;
1642
            }
1643
1644
            return $files;
1645
        } catch (\Throwable $e) {
1646
            if (config('app.debug' === true)) {
1647
                Log::error($e->getTraceAsString());
1648
            }
1649
            $this->_debug('ERROR: Could not open temp dir: '.$e->getMessage());
1650
1651
            return false;
1652
        }
1653
    }
1654
1655
    /**
1656
     * @param $fileLocation
1657
     * @param $fileExtension
1658
     * @return bool
1659
     * @throws \Exception
1660
     */
1661
    protected function _getAudioInfo($fileLocation, $fileExtension): bool
1662
    {
1663
        // Return values.
1664
        $retVal = $audVal = false;
1665
1666
        // Check if audio sample fetching is on.
1667
        if (! $this->_processAudioSample) {
1668
            $audVal = true;
1669
        }
1670
1671
        // Check if media info fetching is on.
1672
        if (! $this->_processAudioInfo) {
1673
            $retVal = true;
1674
        }
1675
1676
        $rQuery = Release::query()->where('proc_pp', '=', 0)->where('id', $this->_release->id)->select(['searchname', 'fromname', 'categories_id'])->first();
1677
1678
        $musicParent = (string) Category::MUSIC_ROOT;
1679
        if ($rQuery === null || ! preg_match(
1680
                sprintf(
1681
                    '/%d\d{3}|%d|%d|%d/',
1682
                    $musicParent[0],
1683
                    Category::OTHER_MISC,
1684
                    Category::MOVIE_OTHER,
1685
                    Category::TV_OTHER
1686
                ),
1687
                $rQuery->id
1688
            )
1689
        ) {
1690
            return false;
1691
        }
1692
1693
        if (File::isFile($fileLocation)) {
1694
1695
            // Check if media info is enabled.
1696
            if (! $retVal) {
1697
1698
                // Get the media info for the file.
1699
                $xmlArray = $this->mediaInfo->getInfo($fileLocation, false);
1700
1701
                if ($xmlArray !== null) {
1702
                    foreach ($xmlArray->getAudios() as $track) {
1703
                        if ($track->get('album') !== null && $track->get('performer') !== null) {
1704
                            if ((int) $this->_release->predb_id === 0 && config('nntmux.rename_music_mediainfo')) {
1705
                                // Make the extension upper case.
1706
                                $ext = strtoupper($fileExtension);
1707
1708
                                // Form a new search name.
1709
                                if (! empty($track->get('recorded_date')) && preg_match('/(?:19|20)\d\d/', $track->get('recorded_date')->getFullname(), $Year)) {
1710
                                    $newName = $track->get('performer')->getFullName().' - '.$track->get('album')->getFullName().' ('.$Year[0].') '.$ext;
1711
                                } else {
1712
                                    $newName = $track->get('performer')->getFullName().' - '.$track->get('album')->getFullName().' '.$ext;
1713
                                }
1714
1715
                                // Get the category or try to determine it.
1716
                                if ($ext === 'MP3') {
1717
                                    $newCat = Category::MUSIC_MP3;
1718
                                } elseif ($ext === 'FLAC') {
1719
                                    $newCat = Category::MUSIC_LOSSLESS;
1720
                                } else {
1721
                                    $newCat = $this->_categorize->determineCategory($rQuery->groups_id, $newName, $rQuery->fromname);
1722
                                }
1723
1724
                                $newTitle = escapeString(substr($newName, 0, 255));
1725
                                // Update the search name.
1726
                                DB::update(
1727
                                        sprintf(
1728
                                            '
1729
											UPDATE releases
1730
											SET searchname = %s, categories_id = %d, iscategorized = 1, isrenamed = 1, proc_pp = 1
1731
											WHERE id = %d',
1732
                                            $newTitle,
1733
                                            $newCat['categories_id'],
1734
                                            $this->_release->id
1735
                                        )
1736
                                    );
1737
                                $release = Release::find($this->_release->id);
1738
                                $release->retag($newCat['tags']);
1739
                                $this->sphinx->updateRelease($this->_release->id);
1740
1741
                                // Echo the changed name.
1742
                                if ($this->_echoCLI) {
1743
                                    NameFixer::echoChangedReleaseName(
1744
                                            [
1745
                                                'new_name' => $newName,
1746
                                                'old_name' => $rQuery->searchname,
1747
                                                'new_category' => $newCat,
1748
                                                'old_category' => $rQuery->id,
1749
                                                'group' => $rQuery->groups_id,
1750
                                                'releases_id' => $this->_release->id,
1751
                                                'method' => 'ProcessAdditional->_getAudioInfo',
1752
                                            ]
1753
                                        );
1754
                                }
1755
                            }
1756
1757
                            // Add the media info.
1758
                            $this->_releaseExtra->addFromXml($this->_release->id, $xmlArray);
1759
1760
                            $retVal = true;
1761
                            $this->_foundAudioInfo = true;
1762
                            if ($this->_echoCLI) {
1763
                                $this->_echo('a', 'primaryOver');
1764
                            }
1765
                            break;
1766
                        }
1767
                    }
1768
                }
1769
            }
1770
1771
            // Check if creating audio samples is enabled.
1772
            if (! $audVal) {
1773
1774
                // File name to store audio file.
1775
                $audioFileName = ($this->_release->guid.'.ogg');
1776
1777
                // Create an audio sample.
1778
                if ($this->ffprobe->isValid($fileLocation)) {
1779
                    try {
1780
                        $audioSample = $this->ffmpeg->open($fileLocation);
1781
                        $format = new Vorbis();
1782
                        $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

1782
                        $audioSample->/** @scrutinizer ignore-call */ 
1783
                                      clip(TimeCode::fromSeconds(30), TimeCode::fromSeconds(30));
Loading history...
1783
                        $audioSample->save($format, $this->tmpPath.$audioFileName);
1784
                    } catch (\InvalidArgumentException $e) {
1785
                        if (config('app.debug' === true)) {
1786
                            Log::error($e->getTraceAsString());
1787
                        }
1788
                        //We do nothing, just prevent displaying errors because the file cannot be open(corrupted or incomplete file)
1789
                    }
1790
                }
1791
1792
                // Check if the new file was created.
1793
                if (File::isFile($this->tmpPath.$audioFileName)) {
1794
1795
                    // Try to move the temp audio file.
1796
                    $renamed = File::move($this->tmpPath.$audioFileName, $this->_audioSavePath.$audioFileName);
1797
1798
                    if (! $renamed) {
1799
                        // Try to copy it if it fails.
1800
                        $copied = File::copy($this->tmpPath.$audioFileName, $this->_audioSavePath.$audioFileName);
1801
1802
                        // Delete the old file.
1803
                        File::delete($this->tmpPath.$audioFileName);
1804
1805
                        // If it didn't copy continue.
1806
                        if (! $copied) {
1807
                            return false;
1808
                        }
1809
                    }
1810
1811
                    // Try to set the file perms.
1812
                    @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

1812
                    /** @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...
1813
1814
                    // Update DB to said we got a audio sample.
1815
                    Release::query()->where('id', $this->_release->id)->update(['audiostatus' => 1]);
1816
1817
                    $audVal = $this->_foundAudioSample = true;
1818
1819
                    if ($this->_echoCLI) {
1820
                        $this->_echo('A', 'primaryOver');
1821
                    }
1822
                }
1823
            }
1824
        }
1825
1826
        return $retVal && $audVal;
1827
    }
1828
1829
    /**
1830
     * Try to get JPG picture, resize it and store it on disk.
1831
     *
1832
     * @param string $fileLocation
1833
     */
1834
    protected function _getJPGSample($fileLocation): void
1835
    {
1836
        // Try to resize/move the image.
1837
        $this->_foundJPGSample = (
1838
        $this->_releaseImage->saveImage(
1839
            $this->_release->guid.'_thumb',
1840
            $fileLocation,
1841
            $this->_releaseImage->jpgSavePath,
1842
            650,
1843
            650
1844
        ) === 1
1845
        );
1846
1847
        // If it's successful, tell the DB.
1848
        if ($this->_foundJPGSample) {
1849
            Release::query()->where('id', $this->_release->id)->update(['jpgstatus' => 1]);
1850
        }
1851
    }
1852
1853
    /**
1854
     * @param string $videoLocation
1855
     *
1856
     * @return string
1857
     */
1858
    private function getVideoTime($videoLocation): string
1859
    {
1860
        // Get the real duration of the file.
1861
        if ($this->ffprobe->isValid($videoLocation)) {
1862
            $time = $this->ffprobe->format($videoLocation)->get('duration');
1863
        }
1864
1865
        if (empty($time) || ! preg_match('/time=(\d{1,2}:\d{1,2}:)?(\d{1,2})\.(\d{1,2})\s*bitrate=/i', $time, $numbers)) {
1866
            return '';
1867
        }
1868
1869
        // Reduce the last number by 1, this is to make sure we don't ask avconv/ffmpeg for non existing data.
1870
        if ($numbers[3] > 0) {
1871
            $numbers[3] -= 1;
1872
        } elseif ($numbers[1] > 0) {
1873
            $numbers[2] -= 1;
1874
            $numbers[3] = '99';
1875
        }
1876
1877
        // Manually pad the numbers in case they are 1 number. to get 02 for example instead of 2.
1878
        return '00:00:'.str_pad($numbers[2], 2, '0', STR_PAD_LEFT).'.'.str_pad($numbers[3], 2, '0', STR_PAD_LEFT);
1879
    }
1880
1881
    /**
1882
     * @param string $fileLocation
1883
     * @return bool
1884
     * @throws \Exception
1885
     */
1886
    protected function _getSample(string $fileLocation): bool
1887
    {
1888
        if (! $this->_processThumbnails) {
1889
            return false;
1890
        }
1891
1892
        if (File::isFile($fileLocation)) {
1893
1894
            // Create path to temp file.
1895
            $fileName = ($this->tmpPath.'zzzz'.random_int(5, 12).random_int(5, 12).'.jpg');
1896
1897
            $time = $this->getVideoTime($fileLocation);
1898
1899
            // Create the image.
1900
            if ($this->ffprobe->isValid($fileLocation)) {
1901
                try {
1902
                    $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

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

2004
                            $format->setAudioCodec(/** @scrutinizer ignore-type */ new Vorbis());
Loading history...
2005
                            $videoSample->filters()->resize(new Dimension(320, -1), ResizeFilter::RESIZEMODE_SCALE_HEIGHT);
2006
                            $videoSample->save($format, $fileName);
2007
                        } catch (\InvalidArgumentException $e) {
2008
                            if (config('app.debug' === true)) {
2009
                                Log::error($e->getTraceAsString());
2010
                            }
2011
                            //We do nothing, just prevent displaying errors because the file cannot be open(corrupted or incomplete file)
2012
                        }
2013
                    }
2014
                }
2015
            }
2016
2017
            if (! $newMethod) {
2018
                // If longer than 60 or we could not get the video length, run the old way.
2019
                if ($this->ffprobe->isValid($fileLocation)) {
2020
                    try {
2021
                        $video = $this->ffmpeg->open($fileLocation);
2022
                        $videoSample = $video->clip(TimeCode::fromSeconds(0), TimeCode::fromSeconds($this->_ffMPEGDuration));
2023
                        $format = new Ogg();
2024
                        $format->setAudioCodec(new Vorbis());
2025
                        $videoSample->filters()->resize(new Dimension(320, -1), ResizeFilter::RESIZEMODE_SCALE_HEIGHT);
2026
                        $videoSample->save($format, $fileName);
2027
                    } catch (\InvalidArgumentException $e) {
2028
                        if (config('app.debug' === true)) {
2029
                            Log::error($e->getTraceAsString());
2030
                        }
2031
                        //We do nothing, just prevent displaying errors because the file cannot be open(corrupted or incomplete file)
2032
                    }
2033
                }
2034
            }
2035
2036
            // Until we find the video file.
2037
            if (File::isFile($fileName)) {
2038
2039
                // Create a path to where the file should be moved.
2040
                $newFile = ($this->_releaseImage->vidSavePath.$this->_release->guid.'.ogv');
2041
2042
                // Try to move the file to the new path.
2043
                $renamed = @File::move($fileName, $newFile);
2044
2045
                // If we couldn't rename it, try to copy it.
2046
                if (! $renamed) {
2047
                    $copied = @File::copy($fileName, $newFile);
2048
2049
                    // Delete the old file.
2050
                    File::delete($fileName);
2051
2052
                    // If it didn't copy, continue.
2053
                    if (! $copied) {
2054
                        return false;
2055
                    }
2056
                }
2057
2058
                // Change the permissions.
2059
                @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

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