Passed
Push — dev ( 2f7d69...0bff7d )
by Darko
06:44
created

ProcessAdditional::_getMediaInfo()   B

Complexity

Conditions 7
Paths 13

Size

Total Lines 38
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

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

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

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

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

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

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

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

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

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

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

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

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

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