Completed
Push — dev ( c43a22...4665f2 )
by Darko
14:27 queued 05:29
created

ProcessAdditional::_finalizeRelease()   B

Complexity

Conditions 10
Paths 64

Size

Total Lines 61
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
cc 10
eloc 36
nc 64
nop 0
dl 0
loc 61
ccs 0
cts 31
cp 0
crap 110
rs 7.6666
c 0
b 0
f 0

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

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

818
        $nzbContents = Utility::unzipGzipFile(/** @scrutinizer ignore-type */ $nzbPath);
Loading history...
819
        if (! $nzbContents) {
0 ignored issues
show
introduced by
The condition $nzbContents is always false.
Loading history...
820
            $this->_echo('NZB is empty or broken for GUID: '.$this->_release->guid.', deleting the release.', 'warning');
821
822
            $this->_deleteRelease();
823
        }
824
825
        // Get a list of files in the nzb.
826
        $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

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

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

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

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

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

Loading history...
1103
                    if ($this->_echoCLI) {
1104
                        $this->_echo('r', 'primaryOver');
1105
                    }
1106
1107
                    if (! $this->_extractUsingRarInfo && $this->_unrarPath !== false) {
1108
                        $fileName = $this->tmpPath.uniqid('', true).'.rar';
1109
                        File::put($fileName, $compressedData);
1110
                        runCmd($this->_killString.$this->_unrarPath.'" e -ai -ep -c- -id -inul -kb -or -p- -r -y "'.$fileName.'" "'.$this->tmpPath.'unrar/"');
1111
                        File::delete($fileName);
1112
                    }
1113
                    break;
1114
                case ArchiveInfo::TYPE_ZIP:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

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

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

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

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

Loading history...
1115
                    if ($this->_echoCLI) {
1116
                        $this->_echo('z', 'primaryOver');
1117
                    }
1118
1119
                    if (! $this->_extractUsingRarInfo && $this->_7zipPath !== false) {
1120
                        $fileName = $this->tmpPath.uniqid('', true).'.zip';
1121
                        File::put($fileName, $compressedData);
1122
                        runCmd($this->_killString.$this->_7zipPath.'" x "'.$fileName.'" -bd -y -o"'.$this->tmpPath.'unzip/"');
1123
                        File::delete($fileName);
1124
                    }
1125
                    break;
1126
                default:
1127
                    return false;
1128
            }
1129
1130
        return $this->_processCompressedFileList();
1131
    }
1132
1133
    /**
1134
     * Get a list of all files in the compressed file, add the file info to the DB.
1135
     *
1136
     * @return bool
1137
     * @throws \Exception
1138
     */
1139
    protected function _processCompressedFileList(): bool
1140
    {
1141
        // Get a list of files inside the Compressed file.
1142
        $files = $this->_archiveInfo->getArchiveFileList();
1143
        if (! \is_array($files) || \count($files) === 0) {
0 ignored issues
show
introduced by
The condition is_array($files) is always true.
Loading history...
1144
            return false;
1145
        }
1146
1147
        // Loop through the files.
1148
        foreach ($files as $file) {
1149
            if ($this->_releaseHasPassword) {
1150
                break;
1151
            }
1152
1153
            if (isset($file['name'])) {
1154
                if (isset($file['error'])) {
1155
                    if (config('app.debug') === true) {
1156
                        $this->_debug("Error: {$file['error']} (in: {$file['source']})");
1157
                    }
1158
                    continue;
1159
                }
1160
1161
                if (isset($file['pass']) && $file['pass'] === true) {
1162
                    $this->_releaseHasPassword = true;
1163
                    $this->_passwordStatus[] = Releases::PASSWD_RAR;
1164
                    break;
1165
                }
1166
1167
                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

1167
                if ($this->_innerFileBlacklist !== false && preg_match(/** @scrutinizer ignore-type */ $this->_innerFileBlacklist, $file['name'])) {
Loading history...
1168
                    $this->_releaseHasPassword = true;
1169
                    $this->_passwordStatus[] = Releases::PASSWD_POTENTIAL;
1170
                    break;
1171
                }
1172
1173
                $fileName = [];
1174
                if (preg_match('/[^\/\\\\]*\.[a-zA-Z0-9]*$/', $file['name'], $fileName)) {
1175
                    $fileName = $fileName[0];
1176
                } else {
1177
                    $fileName = '';
1178
                }
1179
1180
                if ($this->_extractUsingRarInfo) {
1181
                    // Extract files from the rar.
1182
                    if (isset($file['compressed']) && (int) $file['compressed'] === 0) {
1183
                        File::put(
1184
                            $this->tmpPath.random_int(10, 999999).'_'.$fileName,
1185
                            $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

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

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

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

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

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

Loading history...
1341
                                    $this->_getJPGSample($file);
1342
                                    File::delete($file);
1343
                                    break;
1344
1345
                                case
0 ignored issues
show
Coding Style introduced by
As per coding-style, case should be followed by a single space.

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

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

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

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

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

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

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

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

1798
                        $audioSample->/** @scrutinizer ignore-call */ 
1799
                                      clip(TimeCode::fromSeconds(30), TimeCode::fromSeconds(30));
Loading history...
1799
                        $audioSample->save($format, $this->tmpPath.$audioFileName);
1800
                    } catch (\InvalidArgumentException $e) {
1801
                        if (config('app.debug') === true) {
1802
                            Log::error($e->getTraceAsString());
1803
                        }
1804
                        //We do nothing, just prevent displaying errors because the file cannot be open(corrupted or incomplete file)
1805
                    }
1806
                }
1807
1808
                // Check if the new file was created.
1809
                if (File::isFile($this->tmpPath.$audioFileName)) {
1810
1811
                    // Try to move the temp audio file.
1812
                    $renamed = File::move($this->tmpPath.$audioFileName, $this->_audioSavePath.$audioFileName);
1813
1814
                    if (! $renamed) {
1815
                        // Try to copy it if it fails.
1816
                        $copied = File::copy($this->tmpPath.$audioFileName, $this->_audioSavePath.$audioFileName);
1817
1818
                        // Delete the old file.
1819
                        File::delete($this->tmpPath.$audioFileName);
1820
1821
                        // If it didn't copy continue.
1822
                        if (! $copied) {
1823
                            return false;
1824
                        }
1825
                    }
1826
1827
                    // Try to set the file perms.
1828
                    @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

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

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

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

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