Completed
Push — dev ( e5a31e...27c800 )
by Darko
06:50
created

ProcessAdditional::_processMediaInfoMessageIDs()   C

Complexity

Conditions 14
Paths 42

Size

Total Lines 41
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 0
Metric Value
cc 14
eloc 19
nc 42
nop 0
dl 0
loc 41
ccs 0
cts 16
cp 0
crap 210
rs 6.2666
c 0
b 0
f 0

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1166
                            /** @scrutinizer ignore-type */ $this->_archiveInfo->getFileData($file['name'], $file['source'])
Loading history...
1167
                        );
1168
                    } // If the files are compressed, use a binary extractor.
1169
                    else {
1170
                        $this->_archiveInfo->extractFile($file['name'], $this->tmpPath.random_int(10, 999999).'_'.$fileName);
1171
                    }
1172
                }
1173
            }
1174
            $this->_addFileInfo($file);
1175
        }
1176
        if ($this->_addedFileInfo > 0) {
1177
            if (config('nntmux.elasticsearch_enabled') === true) {
1178
                $new = Release::query()
1179
                    ->where('releases.id', $this->_release->id)
1180
                    ->leftJoin('release_files as rf', 'releases.id', '=', 'rf.releases_id')
1181
                    ->select(['releases.id', 'releases.name', 'releases.searchname', 'releases.fromname', DB::raw('IFNULL(GROUP_CONCAT(rf.name SEPARATOR " "),"") filename')])
0 ignored issues
show
Bug introduced by
The type Blacklight\processing\post\DB was not found. Did you mean DB? If so, make sure to prefix the type with \.
Loading history...
1182
                    ->groupBy('releases.id')
1183
                    ->first();
1184
                if ($new !== null) {
1185
                    $data = [
1186
                        'body' => [
1187
                            'doc' => [
1188
                                'id' => $this->_release->id,
1189
                                'name' => $new->name,
1190
                                'searchname' => $new->searchname,
1191
                                'fromname' => $new->fromname,
1192
                                'filename' => ! empty($new->filename) ? $new->filename : '',
0 ignored issues
show
Bug introduced by
The property filename does not seem to exist on App\Models\Release. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
1193
                            ],
1194
                        ],
1195
1196
                        'index' => 'releases',
1197
                        'id' => $this->_release->id,
1198
                    ];
1199
1200
                    Elasticsearch::update($data);
0 ignored issues
show
Bug introduced by
The type Blacklight\processing\post\Elasticsearch was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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

1825
                        $audioSample->/** @scrutinizer ignore-call */ 
1826
                                      clip(TimeCode::fromSeconds(30), TimeCode::fromSeconds(30));
Loading history...
1826
                        $audioSample->save($format, $this->tmpPath.$audioFileName);
1827
                    } catch (\InvalidArgumentException $e) {
1828
                        if (config('app.debug') === true) {
1829
                            Log::error($e->getTraceAsString());
1830
                        }
1831
                        //We do nothing, just prevent displaying errors because the file cannot be open(corrupted or incomplete file)
1832
                    }
1833
                }
1834
1835
                // Check if the new file was created.
1836
                if (File::isFile($this->tmpPath.$audioFileName)) {
1837
1838
                    // Try to move the temp audio file.
1839
                    $renamed = File::move($this->tmpPath.$audioFileName, $this->_audioSavePath.$audioFileName);
1840
1841
                    if (! $renamed) {
1842
                        // Try to copy it if it fails.
1843
                        $copied = File::copy($this->tmpPath.$audioFileName, $this->_audioSavePath.$audioFileName);
1844
1845
                        // Delete the old file.
1846
                        File::delete($this->tmpPath.$audioFileName);
1847
1848
                        // If it didn't copy continue.
1849
                        if (! $copied) {
1850
                            return false;
1851
                        }
1852
                    }
1853
1854
                    // Try to set the file perms.
1855
                    @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

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

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

2047
                            $format->setAudioCodec(/** @scrutinizer ignore-type */ new Vorbis());
Loading history...
2048
                            $videoSample->filters()->resize(new Dimension(320, -1), ResizeFilter::RESIZEMODE_SCALE_HEIGHT);
2049
                            $videoSample->save($format, $fileName);
2050
                        } catch (\InvalidArgumentException $e) {
2051
                            if (config('app.debug') === true) {
2052
                                Log::error($e->getTraceAsString());
2053
                            }
2054
                            //We do nothing, just prevent displaying errors because the file cannot be open(corrupted or incomplete file)
2055
                        }
2056
                    }
2057
                }
2058
            }
2059
2060
            if (! $newMethod) {
2061
                // If longer than 60 or we could not get the video length, run the old way.
2062
                if ($this->ffprobe->isValid($fileLocation)) {
2063
                    try {
2064
                        $video = $this->ffmpeg->open($fileLocation);
2065
                        $videoSample = $video->clip(TimeCode::fromSeconds(0), TimeCode::fromSeconds($this->_ffMPEGDuration));
2066
                        $format = new Ogg();
2067
                        $format->setAudioCodec(new Vorbis());
2068
                        $videoSample->filters()->resize(new Dimension(320, -1), ResizeFilter::RESIZEMODE_SCALE_HEIGHT);
2069
                        $videoSample->save($format, $fileName);
2070
                    } catch (\InvalidArgumentException $e) {
2071
                        if (config('app.debug') === true) {
2072
                            Log::error($e->getTraceAsString());
2073
                        }
2074
                        //We do nothing, just prevent displaying errors because the file cannot be open(corrupted or incomplete file)
2075
                    }
2076
                }
2077
            }
2078
2079
            // Until we find the video file.
2080
            if (File::isFile($fileName)) {
2081
2082
                // Create a path to where the file should be moved.
2083
                $newFile = ($this->_releaseImage->vidSavePath.$this->_release->guid.'.ogv');
2084
2085
                // Try to move the file to the new path.
2086
                $renamed = @File::move($fileName, $newFile);
2087
2088
                // If we couldn't rename it, try to copy it.
2089
                if (! $renamed) {
2090
                    $copied = @File::copy($fileName, $newFile);
2091
2092
                    // Delete the old file.
2093
                    File::delete($fileName);
2094
2095
                    // If it didn't copy, continue.
2096
                    if (! $copied) {
2097
                        return false;
2098
                    }
2099
                }
2100
2101
                // Change the permissions.
2102
                @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

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