Completed
Push — dev ( 50639f...ec2fed )
by Darko
07:20
created

ProcessAdditional::_getSample()   C

Complexity

Conditions 14
Paths 34

Size

Total Lines 62
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 0
Metric Value
eloc 30
dl 0
loc 62
ccs 0
cts 34
cp 0
rs 6.2666
c 0
b 0
f 0
cc 14
nc 34
nop 1
crap 210

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

433
        $this->mediaInfo->setConfig('use_oldxml_mediainfo_output_format', /** @scrutinizer ignore-type */ true);
Loading history...
434
        $this->mediaInfo->setConfig('command', Settings::settingValue('apps..mediainfopath'));
435
436
        $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...
437
        $this->_maxNestedLevels = (int) Settings::settingValue('..maxnestedlevels') === 0 ? 3 : (int) Settings::settingValue('..maxnestedlevels');
438
        $this->_extractUsingRarInfo = (int) Settings::settingValue('..extractusingrarinfo') !== 0;
439
        $this->_fetchLastFiles = (int) Settings::settingValue('archive.fetch.end') !== 0;
440
441
        $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...
442
        $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...
443
444
        // Pass the binary extractors to ArchiveInfo.
445
        $clients = [];
446
        if (Settings::settingValue('apps..unrarpath') !== '') {
0 ignored issues
show
introduced by
The condition App\Models\Settings::set...pps..unrarpath') !== '' is always true.
Loading history...
447
            $this->_unrarPath = Settings::settingValue('apps..unrarpath');
448
            $clients += [ArchiveInfo::TYPE_RAR => $this->_unrarPath];
449
        }
450
        if (Settings::settingValue('apps..zippath') !== '') {
0 ignored issues
show
introduced by
The condition App\Models\Settings::set...'apps..zippath') !== '' is always true.
Loading history...
451
            $this->_7zipPath = Settings::settingValue('apps..zippath');
452
            $clients += [ArchiveInfo::TYPE_ZIP => $this->_7zipPath];
453
        }
454
        $this->_archiveInfo->setExternalClients($clients);
455
456
        $this->_killString = '"';
457
        if (Settings::settingValue('apps..timeoutpath') !== '' && (int) Settings::settingValue('..timeoutseconds') > 0) {
458
            $this->_killString = (
459
                '"'.Settings::settingValue('apps..timeoutpath').
460
                '" --foreground --signal=KILL '.
461
                Settings::settingValue('..timeoutseconds').' "'
462
            );
463
        }
464
465
        $this->_showCLIReleaseID = (PHP_BINARY.' '.__DIR__.DS.'ProcessAdditional.php ReleaseID: ');
466
467
        // Maximum amount of releases to fetch per run.
468
        $this->_queryLimit =
469
            (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...
470
471
        // Maximum message ID's to download per file type in the NZB (video, jpg, etc).
472
        $this->_segmentsToDownload =
473
            (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...
474
475
        // Maximum message ID's to download for a RAR file.
476
        $this->_maximumRarSegments =
477
            (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...
478
479
        // Maximum RAR files to check for a password before stopping.
480
        $this->_maximumRarPasswordChecks =
481
            (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...
482
483
        $this->_maximumRarPasswordChecks = ($this->_maximumRarPasswordChecks < 1 ? 1 : $this->_maximumRarPasswordChecks);
484
485
        // Maximum size of releases in GB.
486
        $this->_maxSize =
487
            (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...
488
        $this->_maxSize = ($this->_maxSize > 0 ? ('AND r.size < '.($this->_maxSize * 1073741824)) : '');
489
        // Minimum size of releases in MB.
490
        $this->_minSize =
491
            (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...
492
        $this->_minSize = ($this->_minSize > 0 ? ('AND r.size > '.($this->_minSize * 1048576)) : '');
493
494
        // Use the alternate NNTP provider for downloading Message-ID's ?
495
        $this->_alternateNNTP = (int) Settings::settingValue('..alternate_nntp') === 1;
496
497
        $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...
498
499
        $this->_addPAR2Files = (int) Settings::settingValue('..addpar2') !== 0;
500
501
        if (! Settings::settingValue('apps..ffmpegpath')) {
502
            $this->_processAudioSample = $this->_processThumbnails = $this->_processVideo = false;
503
        } else {
504
            $this->_processAudioSample = (int) Settings::settingValue('..saveaudiopreview') !== 0;
505
            $this->_processThumbnails = (int) Settings::settingValue('..processthumbnails') !== 0;
506
            $this->_processVideo = (int) Settings::settingValue('..processvideos') !== 0;
507
        }
508
509
        $this->_processJPGSample = (int) Settings::settingValue('..processjpg') !== 0;
510
        $this->_processMediaInfo = Settings::settingValue('apps..mediainfopath') !== '';
511
        $this->_processAudioInfo = $this->_processMediaInfo;
512
        $this->_processPasswords = ! empty(Settings::settingValue('..checkpasswordedrar')) && ! empty(Settings::settingValue('apps..unrarpath'));
513
514
        $this->_audioSavePath = NN_COVERS.'audiosample'.DS;
515
516
        $this->_audioFileRegex = '\.(AAC|AIFF|APE|AC3|ASF|DTS|FLAC|MKA|MKS|MP2|MP3|RA|OGG|OGM|W64|WAV|WMA)';
517
        $this->_ignoreBookRegex = '/\b(epub|lit|mobi|pdf|sipdf|html)\b.*\.rar(?!.{20,})/i';
518
        $this->_supportFileRegex = '/\.(vol\d{1,3}\+\d{1,3}|par2|srs|sfv|nzb';
519
        $this->_videoFileRegex = '\.(AVI|F4V|IFO|M1V|M2V|M4V|MKV|MOV|MP4|MPEG|MPG|MPGV|MPV|OGV|QT|RM|RMVB|TS|VOB|WMV)';
520
    }
521
522
    /**
523
     * Clear out the main temp path when done.
524
     */
525
    public function __destruct()
526
    {
527
        $this->_clearMainTmpPath();
528
    }
529
530
    /**
531
     * @param string $groupID
532
     * @param string $guidChar
533
     *
534
     * @throws \Exception
535
     */
536
    public function start($groupID = '', $guidChar = ''): void
537
    {
538
        $this->_setMainTempPath($guidChar, $groupID);
539
540
        // Fetch all the releases to work on.
541
        $this->_fetchReleases($groupID, $guidChar);
542
543
        // Check if we have releases to work on.
544
        if ($this->_totalReleases > 0) {
545
            // Echo start time and process description.
546
            $this->_echoDescription();
547
548
            $this->_processReleases();
549
        }
550
    }
551
552
    /**
553
     * @var string Main temp path to work on.
554
     */
555
    protected $_mainTmpPath;
556
557
    /**
558
     * @var string Temp path for current release.
559
     */
560
    protected $tmpPath;
561
562
    /**
563
     * @param $guidChar
564
     * @param string $groupID
565
     * @throws \RuntimeException
566
     * @throws \Exception
567
     */
568
    protected function _setMainTempPath(&$guidChar, &$groupID = ''): void
569
    {
570
        // Set up the temporary files folder location.
571
        $this->_mainTmpPath = (string) Settings::settingValue('..tmpunrarpath');
572
573
        // Check if it ends with a dir separator.
574
        if (! preg_match('/[\/\\\\]$/', $this->_mainTmpPath)) {
575
            $this->_mainTmpPath .= DS;
576
        }
577
578
        // 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.
579
        if ($groupID !== '') {
580
            $this->_mainTmpPath .= ($groupID.DS);
581
        } elseif ($guidChar !== '') {
582
            $this->_mainTmpPath .= ($guidChar.DS);
583
        }
584
585
        if (! File::isDirectory($this->_mainTmpPath)) {
586
            if (! File::makeDirectory($this->_mainTmpPath, 0777, true, true) && ! File::isDirectory($this->_mainTmpPath)) {
587
                throw new \RuntimeException(sprintf('Directory "%s" was not created', $this->_mainTmpPath));
588
            }
589
        }
590
591
        if (! File::isDirectory($this->_mainTmpPath)) {
592
            throw new \RuntimeException('Could not create the tmpunrar folder ('.$this->_mainTmpPath.')');
593
        }
594
595
        $this->_clearMainTmpPath();
596
597
        $this->tmpPath = $this->_mainTmpPath;
598
    }
599
600
    /**
601
     * Clear out old folders/files from the main temp folder.
602
     */
603
    protected function _clearMainTmpPath(): void
604
    {
605
        if ($this->_mainTmpPath !== '') {
606
            $this->_recursivePathDelete(
607
                $this->_mainTmpPath,
608
                // These are folders we don't want to delete.
609
                [
610
                    // This is the actual temp folder.
611
                    $this->_mainTmpPath,
612
                ]
613
            );
614
        }
615
    }
616
617
    /**
618
     * Get all releases that need to be processed.
619
     *
620
     * @param int|string $groupID
621
     * @param string     $guidChar
622
     *
623
     * @void
624
     */
625
    protected function _fetchReleases($groupID, &$guidChar): void
626
    {
627
        $releasesQuery = Release::query()
628
            ->where('releases.nzbstatus', '=', 1)
629
            ->where('releases.passwordstatus', '=', -1)
630
            ->where('releases.haspreview', '=', -1)
631
            ->where('categories.disablepreview', '=', 0);
632
        if ($this->_maxSize > 0) {
633
            $releasesQuery->where('releases.size', '<', $this->_maxSize * 1073741824);
634
        }
635
        if ($this->_minSize > 0) {
636
            $releasesQuery->where('releases.size', '>', $this->_minSize * 1048576);
637
        }
638
        if (! empty($groupID)) {
639
            $releasesQuery->where('releases.groups_id', $groupID);
640
        }
641
        if (! empty($guidChar)) {
642
            $releasesQuery->where('releases.leftguid', $guidChar);
643
        }
644
        $releasesQuery->select(['releases.id', 'releases.id as releases_id', 'releases.guid', 'releases.name', 'releases.size', 'releases.groups_id', 'releases.nfostatus', 'releases.fromname', 'releases.completion', 'releases.categories_id', 'releases.searchname', 'releases.predb_id', 'categories.disablepreview'])
645
            ->leftJoin('categories', 'categories.id', '=', 'releases.categories_id')
646
            ->orderBy('releases.passwordstatus')
647
            ->orderByDesc('releases.postdate')
648
            ->limit($this->_queryLimit);
649
650
        $this->_releases = $releasesQuery->get();
651
        $this->_totalReleases = $this->_releases->count();
652
    }
653
654
    /**
655
     * Output the description and start time.
656
     *
657
     * @void
658
     */
659
    protected function _echoDescription(): void
660
    {
661
        if ($this->_totalReleases > 1 && $this->_echoCLI) {
662
            $this->_echo(
663
                PHP_EOL.
664
                'Additional post-processing, started at: '.
665
                now()->format('D M d, Y G:i a').
666
                PHP_EOL.
667
                'Downloaded: (xB) = yEnc article, f= Failed ;Processing: z = ZIP file, r = RAR file'.
668
                PHP_EOL.
669
                'Added: s = Sample image, j = JPEG image, A = Audio sample, a = Audio MediaInfo, v = Video sample'.
670
                PHP_EOL.
671
                'Added: m = Video MediaInfo, n = NFO, ^ = File details from inside the RAR/ZIP',
672
                'header'
673
            );
674
        }
675
    }
676
677
    /**
678
     * Loop through the releases, processing them 1 at a time.
679
     *
680
     * @throws \RuntimeException
681
     * @throws \Exception
682
     */
683
    protected function _processReleases(): void
684
    {
685
        foreach ($this->_releases as $this->_release) {
686
            $this->_echo(
687
                PHP_EOL.'['.$this->_release->id.']['.
688
                human_filesize($this->_release->size, 1).']',
689
                'primaryOver'
690
            );
691
692
            cli_set_process_title($this->_showCLIReleaseID.$this->_release->id);
693
694
            // Create folder to store temporary files.
695
            if (! $this->_createTempFolder()) {
696
                continue;
697
            }
698
699
            // Get NZB contents.
700
            if (! $this->_getNZBContents()) {
701
                continue;
702
            }
703
704
            // Reset the current release variables.
705
            $this->_resetReleaseStatus();
706
707
            // Go through the files in the NZB, get the amount of book files.
708
            $totalBooks = $this->_processNZBContents();
709
710
            // Check if this NZB is a large collection of books.
711
            $bookFlood = false;
712
            if ($totalBooks > 80 && ($totalBooks * 2) >= \count($this->_nzbContents)) {
713
                $bookFlood = true;
714
            }
715
716
            if ($this->_processPasswords || $this->_processThumbnails || $this->_processMediaInfo || $this->_processAudioInfo || $this->_processVideo
717
            ) {
718
719
                // Process usenet Message-ID downloads.
720
                $this->_processMessageIDDownloads();
721
722
                // Process compressed (RAR/ZIP) files inside the NZB.
723
                if (! $bookFlood && $this->_NZBHasCompressedFile) {
724
                    // Download the RARs/ZIPs, extract the files inside them and insert the file info into the DB.
725
                    $this->_processNZBCompressedFiles();
726
727
                    // Download rar/zip in reverse order, to get the last rar or zip file.
728
                    if ($this->_fetchLastFiles) {
729
                        $this->_processNZBCompressedFiles(true);
730
                    }
731
732
                    if (! $this->_releaseHasPassword) {
733
                        // Process the extracted files to get video/audio samples/etc.
734
                        $this->_processExtractedFiles();
735
                    }
736
                }
737
            }
738
739
            // Update the release to say we processed it.
740
            $this->_finalizeRelease();
741
742
            // Delete all files / folders for this release.
743
            $this->_recursivePathDelete($this->tmpPath);
744
        }
745
        if ($this->_echoCLI) {
746
            echo PHP_EOL;
747
        }
748
    }
749
750
    /**
751
     * Deletes files and folders recursively.
752
     *
753
     * @param string $path          Path to a folder or file.
754
     * @param string[] $ignoredFolders array with paths to folders to ignore.
755
     *
756
     * @void
757
     */
758
    protected function _recursivePathDelete($path, $ignoredFolders = []): void
759
    {
760
        if (File::isDirectory($path)) {
761
            if (\in_array($path, $ignoredFolders, false)) {
762
                return;
763
            }
764
            foreach (File::allFiles($path) as $file) {
765
                $this->_recursivePathDelete($file, $ignoredFolders);
766
            }
767
768
            File::deleteDirectory($path);
769
        } elseif (File::isFile($path)) {
770
            File::delete($path);
771
        }
772
    }
773
774
    /**
775
     * Create a temporary storage folder for the current release.
776
     *
777
     *
778
     * @return bool
779
     * @throws \Exception
780
     */
781
    protected function _createTempFolder(): bool
782
    {
783
        // Per release defaults.
784
        $this->tmpPath = $this->_mainTmpPath.$this->_release->guid.DS;
785
        if (! File::isDirectory($this->tmpPath)) {
786
            if (! File::makeDirectory($this->tmpPath, 0777, true, false) && ! File::isDirectory($this->tmpPath)) {
787
                $this->_echo('Unable to create directory: '.$this->tmpPath, 'warning');
788
789
                return $this->_deleteRelease();
790
            }
791
        }
792
793
        return true;
794
    }
795
796
    /**
797
     * Get list of contents inside a release's NZB file.
798
     *
799
     * @return bool
800
     * @throws \Exception
801
     */
802
    protected function _getNZBContents(): bool
803
    {
804
        $nzbPath = $this->_nzb->NZBPath($this->_release->guid);
805
        if ($nzbPath === false) {
806
            $this->_echo('NZB not found for GUID: '.$this->_release->guid, 'warning');
807
808
            $this->_deleteRelease();
809
        }
810
811
        $nzbContents = Utility::unzipGzipFile($nzbPath);
0 ignored issues
show
Bug introduced by
It seems like $nzbPath can also be of type false; however, parameter $filePath of Blacklight\utility\Utility::unzipGzipFile() 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

811
        $nzbContents = Utility::unzipGzipFile(/** @scrutinizer ignore-type */ $nzbPath);
Loading history...
812
        if (! $nzbContents) {
0 ignored issues
show
introduced by
The condition $nzbContents is always false.
Loading history...
813
            $this->_echo('NZB is empty or broken for GUID: '.$this->_release->guid, 'warning');
814
815
            $this->_deleteRelease();
816
        }
817
818
        // Get a list of files in the nzb.
819
        $this->_nzbContents = $this->_nzb->nzbFileList($nzbContents, ['no-file-key' => false, 'strip-count' => true]);
0 ignored issues
show
Bug introduced by
$nzbContents of type false is incompatible with the type string expected by parameter $nzb of Blacklight\NZB::nzbFileList(). ( Ignorable by Annotation )

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

819
        $this->_nzbContents = $this->_nzb->nzbFileList(/** @scrutinizer ignore-type */ $nzbContents, ['no-file-key' => false, 'strip-count' => true]);
Loading history...
820
        if (\count($this->_nzbContents) === 0) {
821
            $this->_echo('NZB is potentially broken for GUID: '.$this->_release->guid, 'warning');
822
823
            $this->_deleteRelease();
824
        }
825
        // Sort keys.
826
        ksort($this->_nzbContents, SORT_NATURAL);
827
828
        return true;
829
    }
830
831
    /**
832
     * @return bool
833
     * @throws \Exception
834
     */
835
    protected function _deleteRelease(): bool
836
    {
837
        Release::whereId($this->_release->id)->delete();
838
839
        return false;
840
    }
841
842
    /**
843
     * Current file we are working on inside a NZB.
844
     * @var array
845
     */
846
    protected $_currentNZBFile;
847
848
    /**
849
     * Does the current NZB contain a compressed (RAR/ZIP) file?
850
     * @var bool
851
     */
852
    protected $_NZBHasCompressedFile;
853
854
    /**
855
     * Process the files inside the NZB, find Message-ID's to download.
856
     * If we find files with book extensions, return the amount.
857
     *
858
     * @return int
859
     */
860
    protected function _processNZBContents(): int
861
    {
862
        $totalBookFiles = 0;
863
        foreach ($this->_nzbContents as $this->_currentNZBFile) {
864
865
            // Check if it's not a nfo, nzb, par2 etc...
866
            if (preg_match($this->_supportFileRegex.'|nfo\b|inf\b|ofn\b)($|[ ")\]-])(?!.{20,})/i', $this->_currentNZBFile['title'])) {
867
                continue;
868
            }
869
870
            // Check if it's a rar/zip.
871
            if (! $this->_NZBHasCompressedFile &&
872
                preg_match(
873
                    '/\.(part\d+|[r|z]\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',
874
                    $this->_currentNZBFile['title']
875
                )
876
            ) {
877
                $this->_NZBHasCompressedFile = true;
878
            }
879
880
            // Look for a video sample, make sure it's not an image.
881
            if ($this->_processThumbnails && empty($this->_sampleMessageIDs) && stripos($this->_currentNZBFile['title'], 'sample') !== false && ! preg_match('/\.jpe?g$/i', $this->_currentNZBFile['title']) && isset($this->_currentNZBFile['segments'])
882
            ) {
883
                // Get the amount of segments for this file.
884
                $segCount = (\count($this->_currentNZBFile['segments']) - 1);
885
                // If it's more than 1 try to get up to the site specified value of segments.
886
                for ($i = 0; $i < $this->_segmentsToDownload; $i++) {
887
                    if ($i > $segCount) {
888
                        break;
889
                    }
890
                    $this->_sampleMessageIDs[] = (string) $this->_currentNZBFile['segments'][$i];
891
                }
892
            }
893
894
            // Look for a JPG picture, make sure it's not a CD cover.
895
            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'])
896
            ) {
897
                // Get the amount of segments for this file.
898
                $segCount = (\count($this->_currentNZBFile['segments']) - 1);
899
                // If it's more than 1 try to get up to the site specified value of segments.
900
                for ($i = 0; $i < $this->_segmentsToDownload; $i++) {
901
                    if ($i > $segCount) {
902
                        break;
903
                    }
904
                    $this->_JPGMessageIDs[] = (string) $this->_currentNZBFile['segments'][$i];
905
                }
906
            }
907
908
            // Look for a video file, make sure it's not a sample, for MediaInfo.
909
            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])
910
            ) {
911
                $this->_MediaInfoMessageIDs = (string) $this->_currentNZBFile['segments'][0];
912
            }
913
914
            // Look for a audio file.
915
            if ($this->_processAudioInfo && empty($this->_AudioInfoMessageIDs) && preg_match('/'.$this->_audioFileRegex.'[. ")\]]/i', $this->_currentNZBFile['title'], $type) && isset($this->_currentNZBFile['segments'])
916
            ) {
917
                // Get the extension.
918
                $this->_AudioInfoExtension = $type[1];
919
                $this->_AudioInfoMessageIDs = (string) $this->_currentNZBFile['segments'][0];
920
            }
921
922
            // Some releases contain many books, increment this to ignore them later.
923
            if (preg_match($this->_ignoreBookRegex, $this->_currentNZBFile['title'])) {
924
                $totalBookFiles++;
925
            }
926
        }
927
928
        return $totalBookFiles;
929
    }
930
931
    /**
932
     * List of message-id's we have tried for rar/zip files.
933
     * @var array
934
     */
935
    protected $_triedCompressedMids = [];
936
937
    /**
938
     * @param bool $reverse
939
     * @throws \Exception
940
     */
941
    protected function _processNZBCompressedFiles($reverse = false): void
942
    {
943
        $this->_reverse = $reverse;
944
945
        if ($this->_reverse) {
946
            if (! krsort($this->_nzbContents)) {
947
                return;
948
            }
949
        } else {
950
            $this->_triedCompressedMids = [];
951
        }
952
953
        $failed = $downloaded = 0;
954
        // Loop through the files, attempt to find if password-ed and files. Starting with what not to process.
955
        foreach ($this->_nzbContents as $nzbFile) {
956
            // TODO change this to max calculated size, as segments vary in size greatly.
957
            if ($downloaded >= $this->_maximumRarSegments) {
958
                break;
959
            }
960
961
            if ($failed >= $this->_maximumRarPasswordChecks) {
962
                break;
963
            }
964
965
            if ($this->_releaseHasPassword) {
966
                $this->_echo('Skipping processing of rar '.$nzbFile['title'].' it has a password.', 'primaryOver');
967
                break;
968
            }
969
970
            // Probably not a rar/zip.
971
            if (! preg_match(
972
                '/\.(part\d+|[r|z]\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',
973
                $nzbFile['title']
974
            )
975
            ) {
976
                continue;
977
            }
978
979
            // Get message-id's for the rar file.
980
            $segCount = (\count($nzbFile['segments']) - 1);
981
            $mID = [];
982
            for ($i = 0; $i < $this->_maximumRarSegments; $i++) {
983
                if ($i > $segCount) {
984
                    break;
985
                }
986
                $segment = (string) $nzbFile['segments'][$i];
987
                if (! $this->_reverse) {
988
                    $this->_triedCompressedMids[] = $segment;
989
                } elseif (\in_array($segment, $this->_triedCompressedMids, false)) {
990
                    // We already downloaded this file.
991
                    continue 2;
992
                }
993
                $mID[] = $segment;
994
            }
995
            // Nothing to download.
996
            if (empty($mID)) {
997
                continue;
998
            }
999
1000
            // Download the article(s) from usenet.
1001
            $fetchedBinary = $this->_nntp->getMessages($this->_releaseGroupName, $mID, $this->_alternateNNTP);
1002
            if ($this->_nntp::isError($fetchedBinary)) {
1003
                $fetchedBinary = false;
1004
            }
1005
1006
            if ($fetchedBinary !== false) {
1007
1008
                // Echo we downloaded compressed file.
1009
                if ($this->_echoCLI) {
1010
                    $this->_echo('(cB)', 'primaryOver');
1011
                }
1012
1013
                $downloaded++;
1014
1015
                // Process the compressed file.
1016
                $decompressed = $this->_processCompressedData($fetchedBinary);
1017
1018
                if ($decompressed || $this->_releaseHasPassword) {
1019
                    break;
1020
                }
1021
            } else {
1022
                $failed++;
1023
                if ($this->_echoCLI) {
1024
                    $this->_echo('f('.$failed.')', 'warningOver');
1025
                }
1026
            }
1027
        }
1028
    }
1029
1030
    /**
1031
     * Check if the data is a ZIP / RAR file, extract files, get file info.
1032
     *
1033
     * @param string $compressedData
1034
     *
1035
     * @return bool
1036
     * @throws \Exception
1037
     */
1038
    protected function _processCompressedData(&$compressedData): bool
1039
    {
1040
        $this->_compressedFilesChecked++;
1041
        // Give the data to archive info so it can check if it's a rar.
1042
        if (! $this->_archiveInfo->setData($compressedData, true)) {
1043
            if (config('app.debug') === true) {
1044
                $this->_debug('Data is probably not RAR or ZIP.');
1045
            }
1046
1047
            return false;
1048
        }
1049
1050
        // Check if there's an error.
1051
        if ($this->_archiveInfo->error !== '') {
1052
            if (config('app.debug') === true) {
1053
                $this->_debug('ArchiveInfo Error: '.$this->_archiveInfo->error);
1054
            }
1055
1056
            return false;
1057
        }
1058
1059
        try {
1060
            // Get a summary of the compressed file.
1061
            $dataSummary = $this->_archiveInfo->getSummary(true);
1062
        } catch (\Exception $exception) {
1063
            //Log the exception and continue to next item
1064
            if (config('app.debug') === true) {
1065
                Log::warning($exception->getTraceAsString());
1066
            }
1067
1068
            return false;
1069
        }
1070
1071
        // Check if the compressed file is encrypted.
1072
        if (! empty($this->_archiveInfo->isEncrypted) || (isset($dataSummary['is_encrypted']) && (int) $dataSummary['is_encrypted'] !== 0)) {
1073
            if (config('app.debug') === true) {
1074
                $this->_debug('ArchiveInfo: Compressed file has a password.');
1075
            }
1076
            $this->_releaseHasPassword = true;
1077
            $this->_passwordStatus = [Releases::PASSWD_RAR];
1078
1079
            return false;
1080
        }
1081
1082
        switch ($dataSummary['main_type']) {
1083
            case ArchiveInfo::TYPE_RAR:
1084
                if ($this->_echoCLI) {
1085
                    $this->_echo('r', 'primaryOver');
1086
                }
1087
1088
                if (! $this->_extractUsingRarInfo && $this->_unrarPath !== false) {
1089
                    $fileName = $this->tmpPath.uniqid('', true).'.rar';
1090
                    File::put($fileName, $compressedData);
1091
                    runCmd($this->_killString.$this->_unrarPath.'" e -ai -ep -c- -id -inul -kb -or -p- -r -y "'.$fileName.'" "'.$this->tmpPath.'unrar/"');
1092
                    File::delete($fileName);
1093
                }
1094
                break;
1095
            case ArchiveInfo::TYPE_ZIP:
1096
                if ($this->_echoCLI) {
1097
                    $this->_echo('z', 'primaryOver');
1098
                }
1099
1100
                if (! $this->_extractUsingRarInfo && ! empty($this->_7zipPath)) {
1101
                    $fileName = $this->tmpPath.uniqid('', true).'.zip';
1102
                    File::put($fileName, $compressedData);
1103
                    runCmd($this->_killString.$this->_7zipPath.'" x "'.$fileName.'" -bd -y -o"'.$this->tmpPath.'unzip/"');
1104
                    File::delete($fileName);
1105
                }
1106
                break;
1107
            default:
1108
                return false;
1109
        }
1110
1111
        return $this->_processCompressedFileList();
1112
    }
1113
1114
    /**
1115
     * Get a list of all files in the compressed file, add the file info to the DB.
1116
     *
1117
     * @return bool
1118
     * @throws \Exception
1119
     */
1120
    protected function _processCompressedFileList(): bool
1121
    {
1122
        // Get a list of files inside the Compressed file.
1123
        $files = $this->_archiveInfo->getArchiveFileList();
1124
        if (! \is_array($files) || \count($files) === 0) {
0 ignored issues
show
introduced by
The condition is_array($files) is always true.
Loading history...
1125
            return false;
1126
        }
1127
1128
        // Loop through the files.
1129
        foreach ($files as $file) {
1130
            if ($this->_releaseHasPassword) {
1131
                break;
1132
            }
1133
1134
            if (isset($file['name'])) {
1135
                if (isset($file['error'])) {
1136
                    if (config('app.debug') === true) {
1137
                        $this->_debug("Error: {$file['error']} (in: {$file['source']})");
1138
                    }
1139
                    continue;
1140
                }
1141
1142
                if (isset($file['pass']) && $file['pass'] === true) {
1143
                    $this->_releaseHasPassword = true;
1144
                    $this->_passwordStatus = [Releases::PASSWD_RAR];
1145
                    break;
1146
                }
1147
1148
                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

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

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

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

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

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

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

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