Passed
Push — master ( 0980e4...0c25e1 )
by Darko
16:40
created

ProcessAdditional::_getVideo()   F

Complexity

Conditions 18
Paths 2522

Size

Total Lines 106
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 53
dl 0
loc 106
rs 0.7
c 0
b 0
f 0
cc 18
nc 2522
nop 1

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
namespace Blacklight\processing\post;
4
5
use App\Models\Category;
6
use App\Models\Predb;
7
use App\Models\Release;
8
use App\Models\ReleaseFile;
9
use App\Models\Settings;
10
use App\Models\UsenetGroup;
11
use Blacklight\Categorize;
12
use Blacklight\ColorCLI;
13
use Blacklight\ElasticSearchSiteSearch;
14
use Blacklight\ManticoreSearch;
15
use Blacklight\NameFixer;
16
use Blacklight\Nfo;
17
use Blacklight\NNTP;
18
use Blacklight\NZB;
19
use Blacklight\ReleaseExtra;
20
use Blacklight\ReleaseImage;
21
use Blacklight\Releases;
22
use Blacklight\utility\Utility;
23
use dariusiii\rarinfo\ArchiveInfo;
24
use dariusiii\rarinfo\Par2Info;
25
use FFMpeg\Coordinate\Dimension;
26
use FFMpeg\Coordinate\TimeCode;
27
use FFMpeg\FFMpeg;
28
use FFMpeg\FFProbe;
29
use FFMpeg\Filters\Video\ResizeFilter;
30
use FFMpeg\Format\Audio\Vorbis;
31
use FFMpeg\Format\Video\Ogg;
32
use Illuminate\Support\Arr;
33
use Illuminate\Support\Carbon;
34
use Illuminate\Support\Facades\DB;
35
use Illuminate\Support\Facades\File;
36
use Illuminate\Support\Facades\Log;
37
use Mhor\MediaInfo\MediaInfo;
38
39
class ProcessAdditional
40
{
41
    /**
42
     * How many compressed (rar/zip) files to check.
43
     *
44
     * @var int
45
     */
46
    public const maxCompressedFilesToCheck = 10;
47
48
    protected $_releases;
49
50
    /**
51
     * Count of releases to work on.
52
     */
53
    protected int $_totalReleases;
54
55
    protected $_release;
56
57
    protected NZB $_nzb;
58
59
    /**
60
     * List of files with sizes/etc contained in the NZB.
61
     */
62
    protected array $_nzbContents;
63
64
    protected Par2Info $_par2Info;
65
66
    protected ArchiveInfo $_archiveInfo;
67
68
    /**
69
     * @var bool|null|string
70
     */
71
    protected mixed $_innerFileBlacklist;
72
73
    protected int $_maxNestedLevels;
74
75
    /**
76
     * @var string|null
77
     */
78
    protected mixed $_7zipPath;
79
80
    /**
81
     * @var null|string
82
     */
83
    protected mixed $_unrarPath;
84
85
    protected string $_killString;
86
87
    protected string|bool $_showCLIReleaseID;
88
89
    protected int $_queryLimit;
90
91
    protected int $_segmentsToDownload;
92
93
    protected int $_maximumRarSegments;
94
95
    protected int $_maximumRarPasswordChecks;
96
97
    protected string $_maxSize;
98
99
    protected string $_minSize;
100
101
    protected bool $_processThumbnails;
102
103
    protected string $_audioSavePath;
104
105
    protected string $_supportFileRegex;
106
107
    protected bool $_echoCLI;
108
109
    protected NNTP $_nntp;
110
111
    protected Categorize $_categorize;
112
113
    protected NameFixer $_nameFixer;
114
115
    protected ReleaseExtra $_releaseExtra;
116
117
    protected ReleaseImage $_releaseImage;
118
119
    protected Nfo $_nfo;
120
121
    protected bool $_extractUsingRarInfo;
122
123
    protected bool $_alternateNNTP;
124
125
    protected int $_ffMPEGDuration;
126
127
    protected bool $_addPAR2Files;
128
129
    protected bool $_processVideo;
130
131
    protected bool $_processJPGSample;
132
133
    protected bool $_processAudioSample;
134
135
    protected bool $_processMediaInfo;
136
137
    protected bool $_processAudioInfo;
138
139
    protected bool $_processPasswords;
140
141
    protected string $_audioFileRegex;
142
143
    protected string $_ignoreBookRegex;
144
145
    protected string $_videoFileRegex;
146
147
    /**
148
     * Have we created a video file for the current release?
149
     */
150
    protected bool $_foundVideo;
151
152
    /**
153
     * Have we found MediaInfo data for a Video for the current release?
154
     */
155
    protected bool $_foundMediaInfo;
156
157
    /**
158
     * Have we found MediaInfo data for a Audio file for the current release?
159
     */
160
    protected bool $_foundAudioInfo;
161
162
    /**
163
     * Have we created a short Audio file sample for the current release?
164
     */
165
    protected bool $_foundAudioSample;
166
167
    /**
168
     * Extension of the found audio file (MP3/FLAC/etc).
169
     */
170
    protected string $_AudioInfoExtension;
171
172
    /**
173
     * Have we downloaded a JPG file for the current release?
174
     */
175
    protected bool $_foundJPGSample;
176
177
    /**
178
     * Have we created a Video JPG image sample for the current release?
179
     */
180
    protected bool $_foundSample;
181
182
    /**
183
     * Have we found PAR2 info on this release?
184
     */
185
    protected bool $_foundPAR2Info;
186
187
    /**
188
     * Message ID's for found content to download.
189
     */
190
    protected array $_sampleMessageIDs;
191
192
    protected $_JPGMessageIDs;
193
194
    protected $_MediaInfoMessageIDs;
195
196
    protected $_AudioInfoMessageIDs;
197
198
    protected $_RARFileMessageIDs;
199
200
    /**
201
     * Password status of the current release.
202
     */
203
    protected int $_passwordStatus;
204
205
    /**
206
     * Does the current release have a password?
207
     */
208
    protected bool $_releaseHasPassword;
209
210
    /**
211
     * Does the current release have an NFO file?
212
     */
213
    protected bool $_releaseHasNoNFO;
214
215
    /**
216
     * Name of the current release's usenet group.
217
     */
218
    protected string $_releaseGroupName;
219
220
    /**
221
     * Number of file information added to DB (from rar/zip/par2 contents).
222
     */
223
    protected int $_addedFileInfo;
224
225
    /**
226
     * Number of file information we found from RAR/ZIP.
227
     * (if some of it was already in DB, this count goes up, while the count above does not).
228
     */
229
    protected int $_totalFileInfo;
230
231
    /**
232
     * How many compressed (rar/zip) files have we checked.
233
     */
234
    protected int $_compressedFilesChecked;
235
236
    /**
237
     * Should we download the last rar?
238
     */
239
    protected bool $_fetchLastFiles;
240
241
    /**
242
     * Are we downloading the last rar?
243
     */
244
    protected bool $_reverse;
245
246
    protected ManticoreSearch $manticore;
247
248
    private FFMpeg $ffmpeg;
249
250
    private FFProbe $ffprobe;
251
252
    private MediaInfo $mediaInfo;
253
254
    private ElasticSearchSiteSearch $elasticsearch;
255
256
    /**
257
     * ProcessAdditional constructor.
258
     *
259
     *
260
     * @throws \Exception
261
     */
262
    public function __construct()
263
    {
264
        $this->_echoCLI = config('nntmux.echocli');
265
266
        $this->_nntp = new NNTP();
267
268
        $this->_nzb = new NZB();
269
        $this->_archiveInfo = new ArchiveInfo();
270
        $this->_categorize = new Categorize();
271
        $this->_nameFixer = new NameFixer();
272
        $this->_releaseExtra = new ReleaseExtra();
273
        $this->_releaseImage = new ReleaseImage();
274
        $this->_par2Info = new Par2Info();
275
        $this->_nfo = new Nfo();
276
        $this->manticore = new ManticoreSearch();
277
        $this->elasticsearch = new ElasticSearchSiteSearch();
278
        $this->ffmpeg = FFMpeg::create(['timeout' => Settings::settingValue('..timeoutseconds')]);
279
        $this->ffprobe = FFProbe::create();
280
        $this->mediaInfo = new MediaInfo();
281
        $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

281
        $this->mediaInfo->setConfig('use_oldxml_mediainfo_output_format', /** @scrutinizer ignore-type */ true);
Loading history...
282
        $this->mediaInfo->setConfig('command', Settings::settingValue('apps..mediainfopath'));
283
284
        $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...
285
        $this->_maxNestedLevels = (int) Settings::settingValue('..maxnestedlevels') === 0 ? 3 : (int) Settings::settingValue('..maxnestedlevels');
286
        $this->_extractUsingRarInfo = (int) Settings::settingValue('..extractusingrarinfo') !== 0;
287
        $this->_fetchLastFiles = (int) Settings::settingValue('archive.fetch.end') !== 0;
288
289
        $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...
290
        $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...
291
292
        // Pass the binary extractors to ArchiveInfo.
293
        $clients = [];
294
        if (Settings::settingValue('apps..unrarpath') !== '') {
0 ignored issues
show
introduced by
The condition App\Models\Settings::set...pps..unrarpath') !== '' is always true.
Loading history...
295
            $this->_unrarPath = Settings::settingValue('apps..unrarpath');
296
            $clients += [ArchiveInfo::TYPE_RAR => $this->_unrarPath];
297
        }
298
        if (Settings::settingValue('apps..zippath') !== '') {
0 ignored issues
show
introduced by
The condition App\Models\Settings::set...'apps..zippath') !== '' is always true.
Loading history...
299
            $this->_7zipPath = Settings::settingValue('apps..zippath');
300
            $clients += [ArchiveInfo::TYPE_ZIP => $this->_7zipPath];
301
        }
302
        $this->_archiveInfo->setExternalClients($clients);
303
304
        $this->_killString = '"';
305
        if (Settings::settingValue('apps..timeoutpath') !== '' && (int) Settings::settingValue('..timeoutseconds') > 0) {
306
            $this->_killString = (
307
                '"'.Settings::settingValue('apps..timeoutpath').
308
                '" --foreground --signal=KILL '.
309
                Settings::settingValue('..timeoutseconds').' "'
310
            );
311
        }
312
313
        $this->_showCLIReleaseID = (PHP_BINARY.' '.__DIR__.'/ProcessAdditional.php ReleaseID: ');
314
315
        // Maximum amount of releases to fetch per run.
316
        $this->_queryLimit =
317
            (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...
318
319
        // Maximum message ID's to download per file type in the NZB (video, jpg, etc).
320
        $this->_segmentsToDownload =
321
            (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...
322
323
        // Maximum message ID's to download for a RAR file.
324
        $this->_maximumRarSegments =
325
            (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...
326
327
        // Maximum RAR files to check for a password before stopping.
328
        $this->_maximumRarPasswordChecks =
329
            (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...
330
331
        $this->_maximumRarPasswordChecks = (max($this->_maximumRarPasswordChecks, 1));
332
333
        // Maximum size of releases in GB.
334
        $this->_maxSize = (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...
335
        // Minimum size of releases in MB.
336
        $this->_minSize = (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...
337
338
        // Use the alternate NNTP provider for downloading Message-ID's ?
339
        $this->_alternateNNTP = (int) Settings::settingValue('..alternate_nntp') === 1;
340
341
        $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...
342
343
        $this->_addPAR2Files = (int) Settings::settingValue('..addpar2') !== 0;
344
345
        if (! Settings::settingValue('apps..ffmpegpath')) {
346
            $this->_processAudioSample = $this->_processThumbnails = $this->_processVideo = false;
347
        } else {
348
            $this->_processAudioSample = (int) Settings::settingValue('..saveaudiopreview') !== 0;
349
            $this->_processThumbnails = (int) Settings::settingValue('..processthumbnails') !== 0;
350
            $this->_processVideo = (int) Settings::settingValue('..processvideos') !== 0;
351
        }
352
353
        $this->_processJPGSample = (int) Settings::settingValue('..processjpg') !== 0;
354
        $this->_processMediaInfo = Settings::settingValue('apps..mediainfopath') !== '';
355
        $this->_processAudioInfo = $this->_processMediaInfo;
356
        $this->_processPasswords = ! empty(Settings::settingValue('..checkpasswordedrar')) && ! empty(Settings::settingValue('apps..unrarpath'));
357
358
        $this->_audioSavePath = storage_path('covers/audiosample/');
359
360
        $this->_audioFileRegex = '\.(AAC|AIFF|APE|AC3|ASF|DTS|FLAC|MKA|MKS|MP2|MP3|RA|OGG|OGM|W64|WAV|WMA)';
361
        $this->_ignoreBookRegex = '/\b(epub|lit|mobi|pdf|sipdf|html)\b.*\.rar(?!.{20,})/i';
362
        $this->_supportFileRegex = '/\.(vol\d{1,3}\+\d{1,3}|par2|srs|sfv|nzb';
363
        $this->_videoFileRegex = '\.(AVI|F4V|IFO|M1V|M2V|M4V|MKV|MOV|MP4|MPEG|MPG|MPGV|MPV|OGV|QT|RM|RMVB|TS|VOB|WMV)';
364
    }
365
366
    /**
367
     * Clear out the main temp path when done.
368
     */
369
    public function __destruct()
370
    {
371
        $this->_clearMainTmpPath();
372
    }
373
374
    /**
375
     * @throws \Exception
376
     */
377
    public function start(string $groupID = '', string $guidChar = ''): void
378
    {
379
        $this->_setMainTempPath($guidChar, $groupID);
380
381
        // Fetch all the releases to work on.
382
        $this->_fetchReleases($groupID, $guidChar);
383
384
        // Check if we have releases to work on.
385
        if ($this->_totalReleases > 0) {
386
            // Echo start time and process description.
387
            $this->_echoDescription();
388
389
            $this->_processReleases();
390
        }
391
    }
392
393
    /**
394
     * @var string Main temp path to work on.
395
     */
396
    protected string $_mainTmpPath;
397
398
    /**
399
     * @var string Temp path for current release.
400
     */
401
    protected string $tmpPath;
402
403
    /**
404
     * @throws \RuntimeException
405
     * @throws \Exception
406
     */
407
    protected function _setMainTempPath(&$guidChar, string &$groupID = ''): void
408
    {
409
        // Set up the temporary files folder location.
410
        $this->_mainTmpPath = (string) Settings::settingValue('..tmpunrarpath');
411
412
        // Check if it ends with a dir separator.
413
        if (! preg_match('/[\/\\\\]$/', $this->_mainTmpPath)) {
414
            $this->_mainTmpPath .= '/';
415
        }
416
417
        // 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.
418
        if ($groupID !== '') {
419
            $this->_mainTmpPath .= ($groupID.'/');
420
        } elseif ($guidChar !== '') {
421
            $this->_mainTmpPath .= ($guidChar.'/');
422
        }
423
424
        if (! File::isDirectory($this->_mainTmpPath)) {
425
            if (! File::makeDirectory($this->_mainTmpPath, 0777, true, true) && ! File::isDirectory($this->_mainTmpPath)) {
426
                throw new \RuntimeException(sprintf('Directory "%s" was not created', $this->_mainTmpPath));
427
            }
428
        }
429
430
        if (! File::isDirectory($this->_mainTmpPath)) {
431
            throw new \RuntimeException('Could not create the tmpunrar folder ('.$this->_mainTmpPath.')');
432
        }
433
434
        $this->_clearMainTmpPath();
435
436
        $this->tmpPath = $this->_mainTmpPath;
437
    }
438
439
    /**
440
     * Clear out old folders/files from the main temp folder.
441
     */
442
    protected function _clearMainTmpPath(): void
443
    {
444
        if ($this->_mainTmpPath !== '') {
445
            $this->_recursivePathDelete(
446
                $this->_mainTmpPath,
447
                // These are folders we don't want to delete.
448
                [
449
                    // This is the actual temp folder.
450
                    $this->_mainTmpPath,
451
                ]
452
            );
453
        }
454
    }
455
456
    /**
457
     * Get all releases that need to be processed.
458
     *
459
     *
460
     * @void
461
     */
462
    protected function _fetchReleases(int|string $groupID, string &$guidChar): void
463
    {
464
        $releasesQuery = Release::query()
465
            ->where('releases.nzbstatus', '=', 1)
466
            ->where('releases.passwordstatus', '=', -1)
467
            ->where('releases.haspreview', '=', -1)
468
            ->where('categories.disablepreview', '=', 0);
469
        if ($this->_maxSize > 0) {
470
            $releasesQuery->where('releases.size', '<', (int) $this->_maxSize * 1073741824);
471
        }
472
        if ($this->_minSize > 0) {
473
            $releasesQuery->where('releases.size', '>', (int) $this->_minSize * 1048576);
474
        }
475
        if (! empty($groupID)) {
476
            $releasesQuery->where('releases.groups_id', $groupID);
477
        }
478
        if (! empty($guidChar)) {
479
            $releasesQuery->where('releases.leftguid', $guidChar);
480
        }
481
        $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'])
482
            ->leftJoin('categories', 'categories.id', '=', 'releases.categories_id')
483
            ->orderBy('releases.passwordstatus')
484
            ->orderByDesc('releases.postdate')
485
            ->limit($this->_queryLimit);
486
487
        $this->_releases = $releasesQuery->get();
488
        $this->_totalReleases = $this->_releases->count();
489
    }
490
491
    /**
492
     * Output the description and start time.
493
     *
494
     * @void
495
     */
496
    protected function _echoDescription(): void
497
    {
498
        if ($this->_totalReleases > 1 && $this->_echoCLI) {
499
            $this->_echo(
500
                PHP_EOL.
501
                'Additional post-processing, started at: '.
502
                now()->format('D M d, Y G:i a').
503
                PHP_EOL.
504
                'Downloaded: (xB) = yEnc article, f= Failed ;Processing: z = ZIP file, r = RAR file'.
505
                PHP_EOL.
506
                'Added: s = Sample image, j = JPEG image, A = Audio sample, a = Audio MediaInfo, v = Video sample'.
507
                PHP_EOL.
508
                'Added: m = Video MediaInfo, n = NFO, ^ = File details from inside the RAR/ZIP',
509
                'header'
510
            );
511
        }
512
    }
513
514
    /**
515
     * Loop through the releases, processing them 1 at a time.
516
     *
517
     * @throws \RuntimeException
518
     * @throws \Exception
519
     */
520
    protected function _processReleases(): void
521
    {
522
        foreach ($this->_releases as $this->_release) {
523
            $this->_echo(
524
                PHP_EOL.'['.$this->_release->id.']['.
525
                human_filesize($this->_release->size, 1).']',
526
                'primaryOver'
527
            );
528
529
            cli_set_process_title($this->_showCLIReleaseID.$this->_release->id);
530
531
            // Create folder to store temporary files.
532
            if (! $this->_createTempFolder()) {
533
                continue;
534
            }
535
536
            // Get NZB contents.
537
            if (! $this->_getNZBContents()) {
538
                continue;
539
            }
540
541
            // Reset the current release variables.
542
            $this->_resetReleaseStatus();
543
544
            // Go through the files in the NZB, get the amount of book files.
545
            $totalBooks = $this->_processNZBContents();
546
547
            // Check if this NZB is a large collection of books.
548
            $bookFlood = false;
549
            if ($totalBooks > 80 && ($totalBooks * 2) >= \count($this->_nzbContents)) {
550
                $bookFlood = true;
551
            }
552
553
            if ($this->_processPasswords || $this->_processThumbnails || $this->_processMediaInfo || $this->_processAudioInfo || $this->_processVideo
554
            ) {
555
                // Process usenet Message-ID downloads.
556
                $this->_processMessageIDDownloads();
557
558
                // Process compressed (RAR/ZIP) files inside the NZB.
559
                if (! $bookFlood && $this->_NZBHasCompressedFile) {
560
                    // Download the RARs/ZIPs, extract the files inside them and insert the file info into the DB.
561
                    $this->_processNZBCompressedFiles();
562
563
                    // Download rar/zip in reverse order, to get the last rar or zip file.
564
                    if ($this->_fetchLastFiles) {
565
                        $this->_processNZBCompressedFiles(true);
566
                    }
567
568
                    if (! $this->_releaseHasPassword) {
569
                        // Process the extracted files to get video/audio samples/etc.
570
                        $this->_processExtractedFiles();
571
                    }
572
                }
573
            }
574
575
            // Update the release to say we processed it.
576
            $this->_finalizeRelease();
577
578
            // Delete all files / folders for this release.
579
            $this->_recursivePathDelete($this->tmpPath);
580
        }
581
        if ($this->_echoCLI) {
582
            echo PHP_EOL;
583
        }
584
    }
585
586
    protected function _recursivePathDelete(string $path, array $ignoredFolders = []): void
587
    {
588
        if (File::isDirectory($path)) {
589
            if (\in_array($path, $ignoredFolders, false)) {
590
                return;
591
            }
592
            foreach (File::allFiles($path) as $file) {
593
                $this->_recursivePathDelete($file, $ignoredFolders);
594
            }
595
596
            File::deleteDirectory($path);
597
        } elseif (File::isFile($path)) {
598
            File::delete($path);
599
        }
600
    }
601
602
    /**
603
     * Create a temporary storage folder for the current release.
604
     *
605
     *
606
     *
607
     * @throws \Exception
608
     */
609
    protected function _createTempFolder(): bool
610
    {
611
        // Per release defaults.
612
        $this->tmpPath = $this->_mainTmpPath.$this->_release->guid.'/';
613
        if (! File::isDirectory($this->tmpPath)) {
614
            if (! File::makeDirectory($this->tmpPath, 0777, true, false) && ! File::isDirectory($this->tmpPath)) {
615
                $this->_echo('Unable to create directory: '.$this->tmpPath, 'warning');
616
                $this->_deleteRelease();
617
618
                return false;
619
            }
620
        }
621
622
        return true;
623
    }
624
625
    /**
626
     * Get list of contents inside a release's NZB file.
627
     *
628
     *
629
     * @throws \Exception
630
     */
631
    protected function _getNZBContents(): bool
632
    {
633
        $nzbPath = $this->_nzb->NZBPath($this->_release->guid);
634
        if ($nzbPath !== false) {
635
            $nzbContents = Utility::unzipGzipFile($nzbPath);
636
            if (! $nzbContents) {
0 ignored issues
show
introduced by
The condition $nzbContents is always false.
Loading history...
637
                $this->_echo('NZB is empty or broken for GUID: '.$this->_release->guid, 'warning');
638
                $this->_deleteRelease();
639
640
                return false;
641
            }
642
            // Get a list of files in the nzb.
643
            $this->_nzbContents = $this->_nzb->nzbFileList($nzbContents, ['no-file-key' => false, 'strip-count' => true]);
644
            if (\count($this->_nzbContents) === 0) {
645
                $this->_echo('NZB is potentially broken for GUID: '.$this->_release->guid, 'warning');
646
                $this->_deleteRelease();
647
648
                return false;
649
            }
650
            // Sort keys.
651
            ksort($this->_nzbContents, SORT_NATURAL);
652
653
            return true;
654
        }
655
        $this->_echo('NZB not found for GUID: '.$this->_release->guid, 'warning');
656
        $this->_deleteRelease();
657
658
        return false;
659
    }
660
661
    protected function _deleteRelease(): void
662
    {
663
        Release::whereId($this->_release->id)->delete();
664
    }
665
666
    /**
667
     * Current file we are working on inside a NZB.
668
     */
669
    protected array $_currentNZBFile;
670
671
    /**
672
     * Does the current NZB contain a compressed (RAR/ZIP) file?
673
     */
674
    protected bool $_NZBHasCompressedFile;
675
676
    /**
677
     * Process the files inside the NZB, find Message-ID's to download.
678
     * If we find files with book extensions, return the amount.
679
     */
680
    protected function _processNZBContents(): int
681
    {
682
        $totalBookFiles = 0;
683
        foreach ($this->_nzbContents as $this->_currentNZBFile) {
684
            try {
685
                // Check if it's not a nfo, nzb, par2 etc...
686
                if (preg_match($this->_supportFileRegex.'|nfo\b|inf\b|ofn\b)($|[ ")\]-])(?!.{20,})/i', $this->_currentNZBFile['title'])) {
687
                    continue;
688
                }
689
690
                // Check if it's a rar/zip.
691
                if (! $this->_NZBHasCompressedFile &&
692
                    preg_match(
693
                        '/\.(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',
694
                        $this->_currentNZBFile['title']
695
                    )
696
                ) {
697
                    $this->_NZBHasCompressedFile = true;
698
                }
699
700
                // Look for a video sample, make sure it's not an image.
701
                if ($this->_processThumbnails && empty($this->_sampleMessageIDs) && isset($this->_currentNZBFile['segments']) && stripos($this->_currentNZBFile['title'], 'sample') !== false && ! preg_match('/\.jpe?g$/i', $this->_currentNZBFile['title'])
702
                ) {
703
                    // Get the amount of segments for this file.
704
                    $segCount = (\count($this->_currentNZBFile['segments']) - 1);
705
                    // If it's more than 1 try to get up to the site specified value of segments.
706
                    for ($i = 0; $i < $this->_segmentsToDownload; $i++) {
707
                        if ($i > $segCount) {
708
                            break;
709
                        }
710
                        $this->_sampleMessageIDs[] = (string) $this->_currentNZBFile['segments'][$i];
711
                    }
712
                }
713
714
                // Look for a JPG picture, make sure it's not a CD cover.
715
                if ($this->_processJPGSample && empty($this->_JPGMessageIDs) && isset($this->_currentNZBFile['segments']) && ! preg_match('/flac|lossless|mp3|music|inner-sanctum|sound/i', $this->_releaseGroupName) && preg_match('/\.jpe?g[. ")\]]/i', $this->_currentNZBFile['title'])
716
                ) {
717
                    // Get the amount of segments for this file.
718
                    $segCount = (\count($this->_currentNZBFile['segments']) - 1);
719
                    // If it's more than 1 try to get up to the site specified value of segments.
720
                    for ($i = 0; $i < $this->_segmentsToDownload; $i++) {
721
                        if ($i > $segCount) {
722
                            break;
723
                        }
724
                        $this->_JPGMessageIDs[] = (string) $this->_currentNZBFile['segments'][$i];
725
                    }
726
                }
727
728
                // Look for a video file, make sure it's not a sample, for MediaInfo.
729
                if ($this->_processMediaInfo && empty($this->_MediaInfoMessageIDs) && isset($this->_currentNZBFile['segments'][0]) && stripos($this->_currentNZBFile['title'], 'sample') !== false && preg_match('/'.$this->_videoFileRegex.'[. ")\]]/i', $this->_currentNZBFile['title'])
730
                ) {
731
                    $this->_MediaInfoMessageIDs = (string) $this->_currentNZBFile['segments'][0];
732
                }
733
734
                // Look for an audio file.
735
                if ($this->_processAudioInfo && empty($this->_AudioInfoMessageIDs) && isset($this->_currentNZBFile['segments']) && preg_match('/'.$this->_audioFileRegex.'[. ")\]]/i', $this->_currentNZBFile['title'], $type)
736
                ) {
737
                    // Get the extension.
738
                    $this->_AudioInfoExtension = $type[1];
739
                    $this->_AudioInfoMessageIDs = (string) $this->_currentNZBFile['segments'][0];
740
                }
741
742
                // Some releases contain many books, increment this to ignore them later.
743
                if (preg_match($this->_ignoreBookRegex, $this->_currentNZBFile['title'])) {
744
                    $totalBookFiles++;
745
                }
746
            } catch (\ErrorException $e) {
747
                Log::debug($e->getTraceAsString());
748
            }
749
        }
750
751
        return $totalBookFiles;
752
    }
753
754
    /**
755
     * List of message-id's we have tried for rar/zip files.
756
     */
757
    protected array $_triedCompressedMids = [];
758
759
    /**
760
     * @throws \Exception
761
     */
762
    protected function _processNZBCompressedFiles(bool $reverse = false): void
763
    {
764
        $this->_reverse = $reverse;
765
766
        if ($this->_reverse) {
767
            if (! krsort($this->_nzbContents)) {
768
                return;
769
            }
770
        } else {
771
            $this->_triedCompressedMids = [];
772
        }
773
774
        $failed = $downloaded = 0;
775
        // Loop through the files, attempt to find if password-ed and files. Starting with what not to process.
776
        foreach ($this->_nzbContents as $nzbFile) {
777
            if ($downloaded >= $this->_maximumRarSegments) {
778
                break;
779
            }
780
781
            if ($failed >= $this->_maximumRarPasswordChecks) {
782
                break;
783
            }
784
785
            if ($this->_releaseHasPassword) {
786
                $this->_echo('Skipping processing of rar '.$nzbFile['title'].' it has a password.', 'primaryOver');
787
                break;
788
            }
789
790
            // Probably not a rar/zip.
791
            if (! preg_match(
792
                '/\.(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',
793
                $nzbFile['title']
794
            )
795
            ) {
796
                continue;
797
            }
798
799
            // Get message-id's for the rar file.
800
            $segCount = (\count($nzbFile['segments']) - 1);
801
            $mID = [];
802
            for ($i = 0; $i < $this->_maximumRarSegments; $i++) {
803
                if ($i > $segCount) {
804
                    break;
805
                }
806
                $segment = (string) $nzbFile['segments'][$i];
807
                if (! $this->_reverse) {
808
                    $this->_triedCompressedMids[] = $segment;
809
                } elseif (\in_array($segment, $this->_triedCompressedMids, false)) {
810
                    // We already downloaded this file.
811
                    continue 2;
812
                }
813
                $mID[] = $segment;
814
            }
815
            // Nothing to download.
816
            if (empty($mID)) {
817
                continue;
818
            }
819
820
            // Download the article(s) from usenet.
821
            $fetchedBinary = $this->_nntp->getMessages($this->_releaseGroupName, $mID, $this->_alternateNNTP);
822
            if ($this->_nntp::isError($fetchedBinary)) {
823
                $fetchedBinary = false;
824
            }
825
826
            if ($fetchedBinary !== false) {
827
                // Echo we downloaded compressed file.
828
                if ($this->_echoCLI) {
829
                    $this->_echo('(cB)', 'primaryOver');
830
                }
831
832
                $downloaded++;
833
834
                // Process the compressed file.
835
                $decompressed = $this->_processCompressedData($fetchedBinary);
836
837
                if ($decompressed || $this->_releaseHasPassword) {
838
                    break;
839
                }
840
            } else {
841
                $failed++;
842
                if ($this->_echoCLI) {
843
                    $this->_echo('f('.$failed.')', 'warningOver');
844
                }
845
            }
846
        }
847
    }
848
849
    /**
850
     * Check if the data is a ZIP / RAR file, extract files, get file info.
851
     *
852
     *
853
     * @throws \Exception
854
     */
855
    protected function _processCompressedData(string &$compressedData): bool
856
    {
857
        $this->_compressedFilesChecked++;
858
        // Give the data to archive info so it can check if it's a rar.
859
        if (! $this->_archiveInfo->setData($compressedData, true)) {
860
            if (config('app.debug') === true) {
861
                $this->_debug('Data is probably not RAR or ZIP.');
862
            }
863
864
            return false;
865
        }
866
867
        // Check if there's an error.
868
        if ($this->_archiveInfo->error !== '') {
869
            if (config('app.debug') === true) {
870
                $this->_debug('ArchiveInfo Error: '.$this->_archiveInfo->error);
871
            }
872
873
            return false;
874
        }
875
876
        try {
877
            // Get a summary of the compressed file.
878
            $dataSummary = $this->_archiveInfo->getSummary(true);
879
        } catch (\Exception $exception) {
880
            // Log the exception and continue to next item
881
            if (config('app.debug') === true) {
882
                Log::warning($exception->getTraceAsString());
883
            }
884
885
            return false;
886
        }
887
888
        // Check if the compressed file is encrypted.
889
        if (! empty($this->_archiveInfo->isEncrypted) || (isset($dataSummary['is_encrypted']) && (int) $dataSummary['is_encrypted'] !== 0)) {
890
            if (config('app.debug') === true) {
891
                $this->_debug('ArchiveInfo: Compressed file has a password.');
892
            }
893
            $this->_releaseHasPassword = true;
894
            $this->_passwordStatus = Releases::PASSWD_RAR;
895
896
            return false;
897
        }
898
899
        if ($this->_reverse) {
900
            $fileData = $dataSummary['file_list'] ?? [];
901
            if (! empty($fileData)) {
902
                $rarFileName = Arr::pluck($fileData, 'name');
903
                if (preg_match(NameFixer::PREDB_REGEX, $rarFileName[0], $hit)) {
904
                    $preCheck = Predb::whereTitle($hit[0])->first();
905
                    $this->_release->preid = $preCheck !== null ? $preCheck->value('id') : 0;
906
                    (new NameFixer())->updateRelease($this->_release, $preCheck->title ?? ucwords($hit[0], '.'), 'RarInfo FileName Match', true, 'Filenames, ', true, true, $this->_release->preid);
907
                } elseif (! empty($dataSummary['archives']) && ! empty($dataSummary['archives'][$rarFileName[0]]['file_list'])) {
908
                    $archiveData = $dataSummary['archives'][$rarFileName[0]]['file_list'];
909
                    $archiveFileName = Arr::pluck($archiveData, 'name');
910
                    if (preg_match(NameFixer::PREDB_REGEX, $archiveFileName[0], $match2)) {
911
                        $preCheck = Predb::whereTitle($match2[0])->first();
912
                        $this->_release->preid = $preCheck !== null ? $preCheck->value('id') : 0;
913
                        (new NameFixer())->updateRelease($this->_release, $preCheck->title ?? ucwords($match2[0], '.'), 'RarInfo FileName Match', true, 'Filenames, ', true, true, $this->_release->preid);
914
                    }
915
                }
916
            }
917
        }
918
919
        switch ($dataSummary['main_type']) {
920
            case ArchiveInfo::TYPE_RAR:
921
                if ($this->_echoCLI) {
922
                    $this->_echo('r', 'primaryOver');
923
                }
924
925
                if (! $this->_extractUsingRarInfo && $this->_unrarPath !== false) {
926
                    $fileName = $this->tmpPath.uniqid('', true).'.rar';
927
                    File::put($fileName, $compressedData);
928
                    runCmd($this->_killString.$this->_unrarPath.'" e -ai -ep -c- -id -inul -kb -or -p- -r -y "'.$fileName.'" "'.$this->tmpPath.'unrar/"');
929
                    File::delete($fileName);
930
                }
931
                break;
932
            case ArchiveInfo::TYPE_ZIP:
933
                if ($this->_echoCLI) {
934
                    $this->_echo('z', 'primaryOver');
935
                }
936
937
                if (! $this->_extractUsingRarInfo && ! empty($this->_7zipPath)) {
938
                    $fileName = $this->tmpPath.uniqid('', true).'.zip';
939
                    File::put($fileName, $compressedData);
940
                    // Pass the -p flag to the 7zip command to make sure it doesn't get stuck in password prompt
941
                    runCmd($this->_killString.$this->_7zipPath.'" x "'.$fileName.'" -p -bd -y -o"'.$this->tmpPath.'unzip/"');
942
                    File::delete($fileName);
943
                }
944
                break;
945
            default:
946
                return false;
947
        }
948
949
        return $this->_processCompressedFileList();
950
    }
951
952
    /**
953
     * Get a list of all files in the compressed file, add the file info to the DB.
954
     *
955
     *
956
     * @throws \Exception
957
     */
958
    protected function _processCompressedFileList(): bool
959
    {
960
        // Get a list of files inside the Compressed file.
961
        $files = $this->_archiveInfo->getArchiveFileList();
962
        if (! \is_array($files) || \count($files) === 0) {
0 ignored issues
show
introduced by
The condition is_array($files) is always true.
Loading history...
963
            return false;
964
        }
965
966
        // Loop through the files.
967
        foreach ($files as $file) {
968
            if ($this->_releaseHasPassword) {
969
                break;
970
            }
971
972
            if (isset($file['name'])) {
973
                if (isset($file['error'])) {
974
                    if (config('app.debug') === true) {
975
                        $this->_debug("Error: {$file['error']} (in: {$file['source']})");
976
                    }
977
978
                    continue;
979
                }
980
981
                if (isset($file['pass']) && $file['pass'] === true) {
982
                    $this->_releaseHasPassword = true;
983
                    $this->_passwordStatus = Releases::PASSWD_RAR;
984
                    break;
985
                }
986
987
                if ($this->_innerFileBlacklist !== false && preg_match($this->_innerFileBlacklist, $file['name'])) {
988
                    $this->_releaseHasPassword = true;
989
                    $this->_passwordStatus = Releases::PASSWD_RAR;
990
                    break;
991
                }
992
993
                $fileName = [];
994
                if (preg_match('/[^\/\\\\]*\.[a-zA-Z0-9]*$/', $file['name'], $fileName)) {
995
                    $fileName = $fileName[0];
996
                } else {
997
                    $fileName = '';
998
                }
999
1000
                if ($this->_extractUsingRarInfo) {
1001
                    // Extract files from the rar.
1002
                    if (isset($file['compressed']) && (int) $file['compressed'] === 0) {
1003
                        File::put(
1004
                            $this->tmpPath.random_int(10, 999999).'_'.$fileName,
1005
                            $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

1005
                            /** @scrutinizer ignore-type */ $this->_archiveInfo->getFileData($file['name'], $file['source'])
Loading history...
1006
                        );
1007
                    } // If the files are compressed, use a binary extractor.
1008
                    else {
1009
                        $this->_archiveInfo->extractFile($file['name'], $this->tmpPath.random_int(10, 999999).'_'.$fileName);
1010
                    }
1011
                }
1012
            }
1013
            $this->_addFileInfo($file);
1014
        }
1015
        if ($this->_addedFileInfo > 0) {
1016
            if (config('nntmux.elasticsearch_enabled') === true) {
1017
                $this->elasticsearch->updateRelease($this->_release->id);
1018
            } else {
1019
                $this->manticore->updateRelease($this->_release->id);
1020
            }
1021
        }
1022
1023
        return $this->_totalFileInfo > 0;
1024
    }
1025
1026
    /**
1027
     * @throws \Exception
1028
     */
1029
    protected function _addFileInfo(&$file): void
1030
    {
1031
        // Don't add rar/zip files to the DB.
1032
        if (! isset($file['error']) && isset($file['source']) &&
1033
            ! 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'])
1034
        ) {
1035
            // Cache the amount of files we find in the RAR or ZIP, return this to say we did find RAR or ZIP content.
1036
            // This is so we don't download more RAR or ZIP files for no reason.
1037
            $this->_totalFileInfo++;
1038
1039
            /* Check if we already have the file or not.
1040
             * Also make sure we don't add too many files, some releases have 100's of files, like PS3 releases.
1041
             */
1042
            if ($this->_addedFileInfo < 11 && ReleaseFile::query()->where(['releases_id' => $this->_release->id, 'name' => $file['name'], 'size' => $file['size']])->first() === null) {
1043
                $addReleaseFiles = ReleaseFile::addReleaseFiles($this->_release->id, $file['name'], $file['size'], $file['date'], $file['pass'], '', $file['crc32'] ?? '');
1044
                if (! empty($addReleaseFiles)) {
1045
                    $this->_addedFileInfo++;
1046
1047
                    if ($this->_echoCLI) {
1048
                        $this->_echo('^', 'primaryOver');
1049
                    }
1050
1051
                    // Check for "codec spam"
1052
                    if (preg_match('/alt\.binaries\.movies($|\.divx$)/', $this->_releaseGroupName) &&
1053
                        preg_match('/[\/\\\\]Codec[\/\\\\]Setup\.exe$/i', $file['name'])
1054
                    ) {
1055
                        if (config('app.debug') === true) {
1056
                            $this->_debug('Codec spam found, setting release to potentially passworded.');
1057
                        }
1058
                        $this->_releaseHasPassword = true;
1059
                        $this->_passwordStatus = Releases::PASSWD_RAR;
1060
                    } //Run a PreDB filename check on insert to try and match the release
1061
                    elseif ($file['name'] !== '' && ! str_starts_with($file['name'], '.')) {
1062
                        $this->_release['filename'] = $file['name'];
1063
                        $this->_release['releases_id'] = $this->_release->id;
1064
                        $this->_nameFixer->matchPreDbFiles($this->_release, 1, 1, true);
1065
                    }
1066
                }
1067
            }
1068
        }
1069
    }
1070
1071
    /**
1072
     * Go through all the extracted files in the temp folder and process them.
1073
     *
1074
     * @throws \Exception
1075
     */
1076
    protected function _processExtractedFiles(): void
1077
    {
1078
        $nestedLevels = 0;
1079
1080
        // Go through all the files in the temp folder, look for compressed files, extract them and the nested ones.
1081
        while ($nestedLevels < $this->_maxNestedLevels) {
1082
            // Break out if we checked more than x compressed files.
1083
            if ($this->_compressedFilesChecked >= self::maxCompressedFilesToCheck) {
1084
                break;
1085
            }
1086
1087
            $foundCompressedFile = false;
1088
1089
            // Get all the compressed files in the temp folder.
1090
            $files = $this->_getTempDirectoryContents('/.*\.([rz]\d{2,}|rar|zipx?|0{0,2}1)($|[^a-z0-9])/i');
1091
1092
            if ($files !== false) {
1093
                foreach ($files as $file) {
0 ignored issues
show
Bug introduced by
The expression $files of type string is not traversable.
Loading history...
1094
                    // Check if the file exists.
1095
                    if (File::isFile($file[0])) {
1096
                        $rarData = @File::get($file[0]);
1097
                        if ($rarData !== false) {
1098
                            $this->_processCompressedData($rarData);
1099
                            $foundCompressedFile = true;
1100
                        }
1101
                        File::delete($file[0]);
1102
                    }
1103
                }
1104
            }
1105
1106
            // If we found no compressed files, break out.
1107
            if (! $foundCompressedFile) {
1108
                break;
1109
            }
1110
1111
            $nestedLevels++;
1112
        }
1113
1114
        $fileType = [];
1115
1116
        // Get all the remaining files in the temp dir.
1117
        $files = $this->_getTempDirectoryContents();
1118
        if ($files !== false) {
0 ignored issues
show
introduced by
The condition $files !== false is always true.
Loading history...
1119
            foreach ($files as $file) {
1120
                $file = $file->getPathname();
1121
1122
                // Skip /. and /..
1123
                if (preg_match('/[\/\\\\]\.{1,2}$/', $file)) {
1124
                    continue;
1125
                }
1126
1127
                if (File::isFile($file)) {
1128
                    // Process PAR2 files.
1129
                    if (! $this->_foundPAR2Info && preg_match('/\.par2$/', $file)) {
1130
                        $this->_siftPAR2Info($file);
1131
                    } // Process NFO files.
1132
                    elseif ($this->_releaseHasNoNFO && preg_match('/(\.(nfo|inf|ofn)|info\.txt)$/i', $file)) {
1133
                        $this->_processNfoFile($file);
1134
                    } // Process audio files.
1135
                    elseif ((! $this->_foundAudioInfo || ! $this->_foundAudioSample) && preg_match('/(.*)'.$this->_audioFileRegex.'$/i', $file, $fileType)) {
1136
                        // Try to get audio sample/audio media info.
1137
                        File::move($file, $this->tmpPath.'audiofile.'.$fileType[2]);
1138
                        $this->_getAudioInfo($this->tmpPath.'audiofile.'.$fileType[2], $fileType[2]);
1139
                        File::delete($this->tmpPath.'audiofile.'.$fileType[2]);
1140
                    } // Process JPG files.
1141
                    elseif (! $this->_foundJPGSample && preg_match('/\.jpe?g$/i', $file)) {
1142
                        $this->_getJPGSample($file);
1143
                        File::delete($file);
1144
                    } // Video sample // video clip // video media info.
1145
                    elseif ((! $this->_foundSample || ! $this->_foundVideo || ! $this->_foundMediaInfo) && preg_match('/(.*)'.$this->_videoFileRegex.'$/i', $file)) {
1146
                        $this->_processVideoFile($file);
1147
                    } // Check file's magic info.
1148
                    else {
1149
                        $output = Utility::fileInfo($file);
1150
                        if (! empty($output)) {
1151
                            switch (true) {
1152
                                case ! $this->_foundJPGSample && preg_match('/^JPE?G/i', $output):
1153
                                    $this->_getJPGSample($file);
1154
                                    File::delete($file);
1155
                                    break;
1156
1157
                                case (! $this->_foundMediaInfo || ! $this->_foundSample || ! $this->_foundVideo) && preg_match('/Matroska data|MPEG v4|MPEG sequence, v2|\WAVI\W/i', $output):
1158
                                    $this->_processVideoFile($file);
1159
                                    break;
1160
1161
                                case (! $this->_foundAudioSample || ! $this->_foundAudioInfo) && preg_match('/^FLAC|layer III|Vorbis audio/i', $output, $fileType):
1162
                                    switch ($fileType[0]) {
1163
                                        case 'FLAC':
1164
                                            $fileType = 'FLAC';
1165
                                            break;
1166
                                        case 'layer III':
1167
                                            $fileType = 'MP3';
1168
                                            break;
1169
                                        case 'Vorbis audio':
1170
                                            $fileType = 'OGG';
1171
                                            break;
1172
                                    }
1173
                                    File::move($file, $this->tmpPath.'audiofile.'.$fileType);
1174
                                    $this->_getAudioInfo($this->tmpPath.'audiofile.'.$fileType, $fileType);
1175
                                    File::delete($this->tmpPath.'audiofile.'.$fileType);
1176
                                    break;
1177
1178
                                case ! $this->_foundPAR2Info && stripos($output, 'Parity') === 0:
1179
                                    $this->_siftPAR2Info($file);
1180
                                    break;
1181
                            }
1182
                        }
1183
                    }
1184
                }
1185
            }
1186
        }
1187
    }
1188
1189
    /**
1190
     * Download all binaries from usenet and form samples / get media info / etc from them.
1191
     *
1192
     * @void
1193
     *
1194
     * @throws \Exception
1195
     */
1196
    protected function _processMessageIDDownloads(): void
1197
    {
1198
        $this->_processSampleMessageIDs();
1199
        $this->_processMediaInfoMessageIDs();
1200
        $this->_processAudioInfoMessageIDs();
1201
        $this->_processJPGMessageIDs();
1202
    }
1203
1204
    /**
1205
     * Download and process binaries for sample videos.
1206
     *
1207
     * @void
1208
     *
1209
     * @throws \Exception
1210
     */
1211
    protected function _processSampleMessageIDs(): void
1212
    {
1213
        // Download and process sample image.
1214
        if (! $this->_foundSample || ! $this->_foundVideo) {
1215
            if (! empty($this->_sampleMessageIDs)) {
1216
                // Download it from usenet.
1217
                $sampleBinary = $this->_nntp->getMessages($this->_releaseGroupName, $this->_sampleMessageIDs, $this->_alternateNNTP);
1218
                if ($this->_nntp::isError($sampleBinary)) {
1219
                    $sampleBinary = false;
1220
                }
1221
1222
                if ($sampleBinary !== false) {
1223
                    if ($this->_echoCLI) {
1224
                        $this->_echo('(sB)', 'primaryOver');
1225
                    }
1226
1227
                    // Check if it's more than 40 bytes.
1228
                    if (\strlen($sampleBinary) > 40) {
1229
                        $fileLocation = $this->tmpPath.'sample_'.random_int(0, 99999).'.avi';
1230
                        // Try to create the file.
1231
                        File::put($fileLocation, $sampleBinary);
1232
1233
                        // Try to get a sample picture.
1234
                        if (! $this->_foundSample) {
1235
                            $this->_foundSample = $this->_getSample($fileLocation);
1236
                        }
1237
1238
                        // Try to get a sample video.
1239
                        if (! $this->_foundVideo) {
1240
                            $this->_foundVideo = $this->_getVideo($fileLocation);
1241
                        }
1242
                    }
1243
                } elseif ($this->_echoCLI) {
1244
                    $this->_echo('f', 'warningOver');
1245
                }
1246
            }
1247
        }
1248
    }
1249
1250
    /**
1251
     * Download and process binaries for media info from videos.
1252
     *
1253
     * @void
1254
     *
1255
     * @throws \Exception
1256
     */
1257
    protected function _processMediaInfoMessageIDs(): void
1258
    {
1259
        // Download and process mediainfo. Also try to get a sample if we didn't get one yet.
1260
        if (! $this->_foundMediaInfo || ! $this->_foundSample || ! $this->_foundVideo) {
1261
            if (! empty($this->_MediaInfoMessageIDs)) {
1262
                // Try to download it from usenet.
1263
                $mediaBinary = $this->_nntp->getMessages($this->_releaseGroupName, $this->_MediaInfoMessageIDs, $this->_alternateNNTP);
1264
                if ($this->_nntp::isError($mediaBinary)) {
1265
                    // If error set it to false.
1266
                    $mediaBinary = false;
1267
                }
1268
1269
                if ($mediaBinary !== false) {
1270
                    if ($this->_echoCLI) {
1271
                        $this->_echo('(mB)', 'primaryOver');
1272
                    }
1273
1274
                    // If it's more than 40 bytes...
1275
                    if (\strlen($mediaBinary) > 40) {
1276
                        $fileLocation = $this->tmpPath.'media.avi';
1277
                        // Create a file on the disk with it.
1278
                        File::put($fileLocation, $mediaBinary);
1279
1280
                        // Try to get media info.
1281
                        if (! $this->_foundMediaInfo) {
1282
                            $this->_foundMediaInfo = $this->_getMediaInfo($fileLocation);
1283
                        }
1284
1285
                        // Try to get a sample picture.
1286
                        if (! $this->_foundSample) {
1287
                            $this->_foundSample = $this->_getSample($fileLocation);
1288
                        }
1289
1290
                        // Try to get a sample video.
1291
                        if (! $this->_foundVideo) {
1292
                            $this->_foundVideo = $this->_getVideo($fileLocation);
1293
                        }
1294
                    }
1295
                } elseif ($this->_echoCLI) {
1296
                    $this->_echo('f', 'warningOver');
1297
                }
1298
            }
1299
        }
1300
    }
1301
1302
    /**
1303
     * Download and process binaries for media info from songs.
1304
     *
1305
     * @void
1306
     *
1307
     * @throws \Exception
1308
     */
1309
    protected function _processAudioInfoMessageIDs(): void
1310
    {
1311
        // Download audio file, use media info to try to get the artist / album.
1312
        if (! $this->_foundAudioInfo || ! $this->_foundAudioSample) {
1313
            if (! empty($this->_AudioInfoMessageIDs)) {
1314
                // Try to download it from usenet.
1315
                $audioBinary = $this->_nntp->getMessages($this->_releaseGroupName, $this->_AudioInfoMessageIDs, $this->_alternateNNTP);
1316
                if ($this->_nntp::isError($audioBinary)) {
1317
                    $audioBinary = false;
1318
                }
1319
1320
                if ($audioBinary !== false) {
1321
                    if ($this->_echoCLI) {
1322
                        $this->_echo('(aB)', 'primaryOver');
1323
                    }
1324
1325
                    $fileLocation = $this->tmpPath.'audio.'.$this->_AudioInfoExtension;
1326
                    // Create a file with it.
1327
                    File::put($fileLocation, $audioBinary);
1328
1329
                    // Try to get media info / sample of the audio file.
1330
                    $this->_getAudioInfo($fileLocation, $this->_AudioInfoExtension);
1331
                } elseif ($this->_echoCLI) {
1332
                    $this->_echo('f', 'warningOver');
1333
                }
1334
            }
1335
        }
1336
    }
1337
1338
    /**
1339
     * Download and process binaries for JPG pictures.
1340
     *
1341
     * @void
1342
     *
1343
     * @throws \Exception
1344
     */
1345
    protected function _processJPGMessageIDs(): void
1346
    {
1347
        // Download JPG file.
1348
        if (! $this->_foundJPGSample && ! empty($this->_JPGMessageIDs)) {
1349
            // Try to download it.
1350
            $jpgBinary = $this->_nntp->getMessages($this->_releaseGroupName, $this->_JPGMessageIDs, $this->_alternateNNTP);
1351
            if ($this->_nntp::isError($jpgBinary)) {
1352
                $jpgBinary = false;
1353
            }
1354
1355
            if ($jpgBinary !== false) {
1356
                if ($this->_echoCLI) {
1357
                    $this->_echo('(jB)', 'primaryOver');
1358
                }
1359
1360
                // Try to create a file with it.
1361
                File::put($this->tmpPath.'samplepicture.jpg', $jpgBinary);
1362
1363
                // Check if image exif data is jpeg
1364
                if (exif_imagetype($this->tmpPath.'samplepicture.jpg') === IMAGETYPE_JPEG) {
1365
                    // Try to resize and move it.
1366
                    $this->_foundJPGSample = ($this->_releaseImage->saveImage($this->_release->guid.'_thumb',
1367
                        $this->tmpPath.'samplepicture.jpg', $this->_releaseImage->jpgSavePath, 650, 650) === 1);
1368
1369
                    if ($this->_foundJPGSample) {
1370
                        // Update the DB to say we got it.
1371
                        Release::query()->where('id', $this->_release->id)->update(['jpgstatus' => 1]);
1372
1373
                        if ($this->_echoCLI) {
1374
                            $this->_echo('j', 'primaryOver');
1375
                        }
1376
                    }
1377
1378
                    File::delete($this->tmpPath.'samplepicture.jpg');
1379
                }
1380
            } elseif ($this->_echoCLI) {
1381
                $this->_echo('f', 'warningOver');
1382
            }
1383
        }
1384
    }
1385
1386
    /**
1387
     * Update the release to say we processed it.
1388
     */
1389
    protected function _finalizeRelease(): void
1390
    {
1391
        $updateRows = ['haspreview' => 0];
1392
1393
        // If samples exist from previous runs, set flags.
1394
        if (File::isFile($this->_releaseImage->imgSavePath.$this->_release->guid.'_thumb.jpg')) {
1395
            $updateRows = ['haspreview' => 1];
1396
        }
1397
1398
        if (File::isFile($this->_releaseImage->vidSavePath.$this->_release->guid.'.ogv')) {
1399
            $updateRows += ['videostatus' => 1];
1400
        }
1401
1402
        if (File::isFile($this->_releaseImage->jpgSavePath.$this->_release->guid.'_thumb.jpg')) {
1403
            $updateRows += ['jpgstatus' => 1];
1404
        }
1405
1406
        // Get the amount of files we found inside the RAR/ZIP files.
1407
1408
        $releaseFilesCount = ReleaseFile::whereReleasesId($this->_release->id)->count('releases_id');
1409
1410
        if ($releaseFilesCount === null) {
1411
            $releaseFilesCount = 0;
1412
        }
1413
1414
        $this->_passwordStatus = max([$this->_passwordStatus]);
1415
1416
        // Set the release to no password if password processing is off.
1417
        if (! $this->_processPasswords) {
1418
            $this->_releaseHasPassword = false;
1419
        }
1420
1421
        // If we failed to get anything from the RAR/ZIPs, decrement the passwordstatus, if the rar/zip has no password.
1422
        if (! $this->_releaseHasPassword && $this->_NZBHasCompressedFile && $releaseFilesCount === 0) {
1423
            $release = Release::query()->where('id', $this->_release->id);
1424
            $release->decrement('passwordstatus');
1425
            $release->update(
1426
                $updateRows
1427
            );
1428
        } // Else update the release with the password status (if the admin enabled the setting).
1429
        else {
1430
            $updateRows += ['passwordstatus' => $this->_processPasswords ? $this->_passwordStatus : Releases::PASSWD_NONE,
1431
                'rarinnerfilecount' => $releaseFilesCount, ];
1432
            Release::query()->where('id', $this->_release->id)->update(
1433
                $updateRows
1434
            );
1435
        }
1436
    }
1437
1438
    /**
1439
     * @return bool|string|\Symfony\Component\Finder\SplFileInfo[]
1440
     */
1441
    protected function _getTempDirectoryContents(string $pattern = '', string $path = ''): array|bool|string
1442
    {
1443
        if ($path === '') {
1444
            $path = $this->tmpPath;
1445
        }
1446
1447
        $files = File::allFiles($path);
1448
        try {
1449
            if ($pattern !== '') {
1450
                $allFiles = [];
1451
                foreach ($files as $file) {
1452
                    if (preg_match($pattern, $file->getRelativePathname())) {
1453
                        $allFiles .= $file;
1454
                    }
1455
                }
1456
1457
                return $allFiles;
1458
            }
1459
1460
            return $files;
1461
        } catch (\Throwable $e) {
1462
            if (config('app.debug') === true) {
1463
                Log::error($e->getTraceAsString());
1464
                $this->_debug('ERROR: Could not open temp dir: '.$e->getMessage());
1465
            }
1466
1467
            return false;
1468
        }
1469
    }
1470
1471
    /**
1472
     * @throws \Exception
1473
     */
1474
    protected function _getAudioInfo($fileLocation, $fileExtension): bool
1475
    {
1476
        // Return values.
1477
        $retVal = $audVal = false;
1478
1479
        // Check if audio sample fetching is on.
1480
        if (! $this->_processAudioSample) {
1481
            $audVal = true;
1482
        }
1483
1484
        // Check if media info fetching is on.
1485
        if (! $this->_processAudioInfo) {
1486
            $retVal = true;
1487
        }
1488
1489
        $rQuery = Release::query()->where('proc_pp', '=', 0)->where('id', $this->_release->id)->select(['searchname', 'fromname', 'categories_id'])->first();
1490
1491
        $musicParent = (string) Category::MUSIC_ROOT;
1492
        if ($rQuery === null || ! preg_match(
1493
            sprintf(
1494
                '/%d\d{3}|%d|%d|%d/',
1495
                $musicParent[0],
1496
                Category::OTHER_MISC,
1497
                Category::MOVIE_OTHER,
1498
                Category::TV_OTHER
1499
            ),
1500
            $rQuery->id
1501
        )
1502
        ) {
1503
            return false;
1504
        }
1505
1506
        if (File::isFile($fileLocation)) {
1507
            // Check if media info is enabled.
1508
            if (! $retVal) {
1509
                // Get the media info for the file.
1510
                try {
1511
                    $xmlArray = $this->mediaInfo->getInfo($fileLocation, false);
1512
1513
                    if ($xmlArray !== null) {
1514
                        foreach ($xmlArray->getAudios() as $track) {
1515
                            if ($track->get('album') !== null && $track->get('performer') !== null) {
1516
                                if ((int) $this->_release->predb_id === 0 && config('nntmux.rename_music_mediainfo')) {
1517
                                    // Make the extension upper case.
1518
                                    $ext = strtoupper($fileExtension);
1519
1520
                                    // Form a new search name.
1521
                                    if (! empty($track->get('recorded_date')) && preg_match('/(?:19|20)\d\d/', $track->get('recorded_date')->getFullname(), $Year)) {
1522
                                        $newName = $track->get('performer')->getFullName().' - '.$track->get('album')->getFullName().' ('.$Year[0].') '.$ext;
1523
                                    } else {
1524
                                        $newName = $track->get('performer')->getFullName().' - '.$track->get('album')->getFullName().' '.$ext;
1525
                                    }
1526
1527
                                    // Get the category or try to determine it.
1528
                                    if ($ext === 'MP3') {
1529
                                        $newCat = Category::MUSIC_MP3;
1530
                                    } elseif ($ext === 'FLAC') {
1531
                                        $newCat = Category::MUSIC_LOSSLESS;
1532
                                    } else {
1533
                                        $newCat = $this->_categorize->determineCategory($rQuery->groups_id, $newName, $rQuery->fromname);
1534
                                    }
1535
1536
                                    $newTitle = escapeString(substr($newName, 0, 255));
1537
                                    // Update the search name.
1538
                                    $release = Release::whereId($this->_release->id);
1539
                                    $release->update(['searchname' => $newTitle, 'categories_id' => $newCat['categories_id'], 'iscategorized' => 1, 'isrenamed' => 1, 'proc_pp' => 1]);
1540
1541
                                    if (config('nntmux.elasticsearch_enabled') === true) {
1542
                                        $this->elasticsearch->updateRelease($this->_release->id);
1543
                                    } else {
1544
                                        $this->manticore->updateRelease($this->_release->id);
1545
                                    }
1546
1547
                                    // Echo the changed name.
1548
                                    if ($this->_echoCLI) {
1549
                                        NameFixer::echoChangedReleaseName(
1550
                                            [
1551
                                                'new_name' => $newTitle,
1552
                                                'old_name' => $rQuery->searchname,
1553
                                                'new_category' => $newCat,
1554
                                                'old_category' => $rQuery->id,
1555
                                                'group' => $rQuery->groups_id,
1556
                                                'releases_id' => $this->_release->id,
1557
                                                'method' => 'ProcessAdditional->_getAudioInfo',
1558
                                            ]
1559
                                        );
1560
                                    }
1561
                                }
1562
1563
                                // Add the media info.
1564
                                $this->_releaseExtra->addFromXml($this->_release->id, $xmlArray);
1565
1566
                                $retVal = true;
1567
                                $this->_foundAudioInfo = true;
1568
                                if ($this->_echoCLI) {
1569
                                    $this->_echo('a', 'primaryOver');
1570
                                }
1571
                                break;
1572
                            }
1573
                        }
1574
                    }
1575
                } catch (\RuntimeException $e) {
1576
                    Log::debug($e->getMessage());
1577
                } catch (\TypeError $e) {
1578
                    Log::debug($e->getMessage());
1579
                }
1580
            }
1581
1582
            // Check if creating audio samples is enabled.
1583
            if (! $audVal) {
1584
                // File name to store audio file.
1585
                $audioFileName = ($this->_release->guid.'.ogg');
1586
1587
                // Create an audio sample.
1588
                if ($this->ffprobe->isValid($fileLocation)) {
1589
                    try {
1590
                        $audioSample = $this->ffmpeg->open($fileLocation);
1591
                        $format = new Vorbis();
1592
                        $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

1592
                        $audioSample->/** @scrutinizer ignore-call */ 
1593
                                      clip(TimeCode::fromSeconds(30), TimeCode::fromSeconds(30));
Loading history...
1593
                        $audioSample->save($format, $this->tmpPath.$audioFileName);
1594
                    } catch (\InvalidArgumentException $e) {
1595
                        if (config('app.debug') === true) {
1596
                            Log::error($e->getTraceAsString());
1597
                        }
1598
                        //We do nothing, just prevent displaying errors because the file cannot be open(corrupted or incomplete file)
1599
                    }
1600
                }
1601
1602
                // Check if the new file was created.
1603
                if (File::isFile($this->tmpPath.$audioFileName)) {
1604
                    // Try to move the temp audio file.
1605
                    $renamed = File::move($this->tmpPath.$audioFileName, $this->_audioSavePath.$audioFileName);
1606
1607
                    if (! $renamed) {
1608
                        // Try to copy it if it fails.
1609
                        $copied = File::copy($this->tmpPath.$audioFileName, $this->_audioSavePath.$audioFileName);
1610
1611
                        // Delete the old file.
1612
                        File::delete($this->tmpPath.$audioFileName);
1613
1614
                        // If it didn't copy continue.
1615
                        if (! $copied) {
1616
                            return false;
1617
                        }
1618
                    }
1619
1620
                    // Try to set the file perms.
1621
                    @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

1621
                    /** @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...
1622
1623
                    // Update DB to said we got a audio sample.
1624
                    Release::query()->where('id', $this->_release->id)->update(['audiostatus' => 1]);
1625
1626
                    $audVal = $this->_foundAudioSample = true;
1627
1628
                    if ($this->_echoCLI) {
1629
                        $this->_echo('A', 'primaryOver');
1630
                    }
1631
                }
1632
            }
1633
        }
1634
1635
        return $retVal && $audVal;
1636
    }
1637
1638
    /**
1639
     * Try to get JPG picture, resize it and store it on disk.
1640
     */
1641
    protected function _getJPGSample(string $fileLocation): void
1642
    {
1643
        // Try to resize/move the image.
1644
        $this->_foundJPGSample = (
1645
            $this->_releaseImage->saveImage(
1646
                $this->_release->guid.'_thumb',
1647
                $fileLocation,
1648
                $this->_releaseImage->jpgSavePath,
1649
                650,
1650
                650
1651
            ) === 1
1652
        );
1653
1654
        // If it's successful, tell the DB.
1655
        if ($this->_foundJPGSample) {
1656
            Release::query()->where('id', $this->_release->id)->update(['jpgstatus' => 1]);
1657
        }
1658
    }
1659
1660
    private function getVideoTime(string $videoLocation): string
1661
    {
1662
        // Get the real duration of the file.
1663
        if ($this->ffprobe->isValid($videoLocation)) {
1664
            $time = $this->ffprobe->format($videoLocation)->get('duration');
1665
        }
1666
1667
        if (empty($time) || ! preg_match('/time=(\d{1,2}:\d{1,2}:)?(\d{1,2})\.(\d{1,2})\s*bitrate=/i', $time, $numbers)) {
1668
            return '';
1669
        }
1670
1671
        // Reduce the last number by 1, this is to make sure we don't ask avconv/ffmpeg for non existing data.
1672
        if ($numbers[3] > 0) {
1673
            $numbers[3]--;
1674
        } elseif ($numbers[1] > 0) {
1675
            $numbers[2]--;
1676
            $numbers[3] = '99';
1677
        }
1678
1679
        // Manually pad the numbers in case they are 1 number. to get 02 for example instead of 2.
1680
        return '00:00:'.str_pad($numbers[2], 2, '0', STR_PAD_LEFT).'.'.str_pad($numbers[3], 2, '0', STR_PAD_LEFT);
1681
    }
1682
1683
    /**
1684
     * @throws \Exception
1685
     */
1686
    protected function _getSample(string $fileLocation): bool
1687
    {
1688
        if (! $this->_processThumbnails) {
1689
            return false;
1690
        }
1691
1692
        if (File::isFile($fileLocation)) {
1693
            // Create path to temp file.
1694
            $fileName = ($this->tmpPath.'zzzz'.random_int(5, 12).random_int(5, 12).'.jpg');
1695
1696
            $time = $this->getVideoTime($fileLocation);
1697
1698
            // Create the image.
1699
            if ($this->ffprobe->isValid($fileLocation)) {
1700
                try {
1701
                    $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

1701
                    $this->ffmpeg->open($fileLocation)->/** @scrutinizer ignore-call */ frame(TimeCode::fromString($time === '' ? '00:00:03:00' : $time))->save($fileName);
Loading history...
1702
                } catch (\RuntimeException $runtimeException) {
1703
                    if (config('app.debug') === true) {
1704
                        Log::error($runtimeException->getTraceAsString());
1705
                    }
1706
                    //We show no error we just log it, we failed to save the frame and move on
1707
                } catch (\InvalidArgumentException $e) {
1708
                    if (config('app.debug') === true) {
1709
                        Log::error($e->getTraceAsString());
1710
                    }
1711
                    //We do nothing, just prevent displaying errors because the file cannot be open(corrupted or incomplete file)
1712
                } catch (\Throwable $e) {
1713
                    if (config('app.debug') === true) {
1714
                        Log::error($e->getTraceAsString());
1715
                    }
1716
                    //Again we do nothing, we just want to catch the error
1717
                }
1718
            }
1719
1720
            // Check if the file exists.
1721
            if (File::isFile($fileName)) {
1722
                // Try to resize/move the image.
1723
                $saved = $this->_releaseImage->saveImage(
1724
                    $this->_release->guid.'_thumb',
1725
                    $fileName,
1726
                    $this->_releaseImage->imgSavePath,
1727
                    800,
1728
                    600
1729
                );
1730
1731
                // Delete the temp file we created.
1732
                File::delete($fileName);
1733
1734
                // Check if it saved.
1735
                if ($saved === 1) {
1736
                    if ($this->_echoCLI) {
1737
                        $this->_echo('s', 'primaryOver');
1738
                    }
1739
1740
                    return true;
1741
                }
1742
            }
1743
        }
1744
1745
        return false;
1746
    }
1747
1748
    /**
1749
     * @throws \Exception
1750
     */
1751
    protected function _getVideo(string $fileLocation): bool
1752
    {
1753
        if (! $this->_processVideo) {
1754
            return false;
1755
        }
1756
1757
        // Try to find an avi file.
1758
        if (File::isFile($fileLocation)) {
1759
            // Create a filename to store the temp file.
1760
            $fileName = ($this->tmpPath.'zzzz'.$this->_release->guid.'.ogv');
1761
1762
            $newMethod = false;
1763
            // If wanted sample length is less than 60, try to get sample from the end of the video.
1764
            if ($this->_ffMPEGDuration < 60) {
1765
                // Get the real duration of the file.
1766
                $time = $this->getVideoTime($fileLocation);
1767
1768
                if ($time !== '' && preg_match('/(\d{2}).(\d{2})/', $time, $numbers)) {
1769
                    $newMethod = true;
1770
                    // Get the lowest time we can start making the video at based on how many seconds the admin wants the video to be.
1771
                    if ($numbers[1] <= $this->_ffMPEGDuration) {
1772
                        // If the clip is shorter than the length we want.
1773
                        // The lowest we want is 0.
1774
                        $lowestLength = '00:00:00.00';
1775
                    } else {
1776
                        // If the clip is longer than the length we want.
1777
                        // The lowest we want is the the difference between the max video length and our wanted total time.
1778
                        $lowestLength = ($numbers[1] - $this->_ffMPEGDuration);
1779
                        // Form the time string.
1780
                        $end = '.'.$numbers[2];
1781
                        $lowestLength = match (\strlen($lowestLength)) {
1782
                            1 => ('00:00:0'.$lowestLength.$end),
1783
                            2 => ('00:00:'.$lowestLength.$end),
1784
                            default => '00:00:60.00',
1785
                        };
1786
                    }
1787
1788
                    // Try to get the sample (from the end instead of the start).
1789
                    if ($this->ffprobe->isValid($fileLocation)) {
1790
                        try {
1791
                            $video = $this->ffmpeg->open($fileLocation);
1792
                            $videoSample = $video->clip(TimeCode::fromString($lowestLength), TimeCode::fromSeconds($this->_ffMPEGDuration));
1793
                            $format = new Ogg();
1794
                            $format->setAudioCodec('libvorbis');
1795
                            $videoSample->filters()->resize(new Dimension(320, -1), ResizeFilter::RESIZEMODE_SCALE_HEIGHT);
1796
                            $videoSample->save($format, $fileName);
1797
                        } catch (\InvalidArgumentException $e) {
1798
                            if (config('app.debug') === true) {
1799
                                Log::error($e->getTraceAsString());
1800
                            }
1801
                            //We do nothing, just prevent displaying errors because the file cannot be open(corrupted or incomplete file)
1802
                        }
1803
                    }
1804
                }
1805
            }
1806
1807
            // If longer than 60 or we could not get the video length, run the old way.
1808
            if (! $newMethod && $this->ffprobe->isValid($fileLocation)) {
1809
                try {
1810
                    $video = $this->ffmpeg->open($fileLocation);
1811
                    $videoSample = $video->clip(TimeCode::fromSeconds(0), TimeCode::fromSeconds($this->_ffMPEGDuration));
1812
                    $format = new Ogg();
1813
                    $format->setAudioCodec('libvorbis');
1814
                    $videoSample->filters()->resize(new Dimension(320, -1), ResizeFilter::RESIZEMODE_SCALE_HEIGHT);
1815
                    $videoSample->save($format, $fileName);
1816
                } catch (\InvalidArgumentException $e) {
1817
                    if (config('app.debug') === true) {
1818
                        Log::error($e->getTraceAsString());
1819
                    }
1820
                    //We do nothing, just prevent displaying errors because the file cannot be open(corrupted or incomplete file)
1821
                }
1822
            }
1823
1824
            // Until we find the video file.
1825
            if (File::isFile($fileName)) {
1826
                // Create a path to where the file should be moved.
1827
                $newFile = ($this->_releaseImage->vidSavePath.$this->_release->guid.'.ogv');
1828
1829
                // Try to move the file to the new path.
1830
                // If we couldn't rename it, try to copy it.
1831
                if (! @File::move($fileName, $newFile)) {
1832
                    $copied = @File::copy($fileName, $newFile);
1833
1834
                    // Delete the old file.
1835
                    File::delete($fileName);
1836
1837
                    // If it didn't copy, continue.
1838
                    if (! $copied) {
1839
                        return false;
1840
                    }
1841
                }
1842
1843
                // Change the permissions.
1844
                @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

1844
                /** @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...
1845
1846
                // Update query to say we got the video.
1847
                Release::query()->where('guid', $this->_release->guid)->update(['videostatus' => 1]);
1848
                if ($this->_echoCLI) {
1849
                    $this->_echo('v', 'primaryOver');
1850
                }
1851
1852
                return true;
1853
            }
1854
        }
1855
1856
        return false;
1857
    }
1858
1859
    /**
1860
     * @throws \Exception
1861
     */
1862
    protected function _getMediaInfo($fileLocation): bool
1863
    {
1864
        if (! $this->_processMediaInfo) {
1865
            return false;
1866
        }
1867
1868
        // Look for the video file.
1869
        if (File::isFile($fileLocation)) {
1870
            try {
1871
                $xmlArray = $this->mediaInfo->getInfo($fileLocation, true);
1872
1873
                // Check if we got it.
1874
1875
                if ($xmlArray === null) {
1876
                    return false;
1877
                }
1878
1879
                // Insert it into the DB.
1880
                $this->_releaseExtra->addFull($this->_release->id, $xmlArray);
1881
                $this->_releaseExtra->addFromXml($this->_release->id, $xmlArray);
1882
1883
                if ($this->_echoCLI) {
1884
                    $this->_echo('m', 'primaryOver');
1885
                }
1886
1887
                return true;
1888
            } catch (\RuntimeException $e) {
1889
                Log::debug($e->getMessage());
1890
1891
                return false;
1892
            } catch (\TypeError $e) {
1893
                Log::debug($e->getMessage());
1894
1895
                return false;
1896
            } catch (\ErrorException $e) {
1897
                Log::debug($e->getMessage());
1898
1899
                return false;
1900
            }
1901
        }
1902
1903
        return false;
1904
    }
1905
1906
    /**
1907
     * @throws \Exception
1908
     */
1909
    protected function _siftPAR2Info($fileLocation): void
1910
    {
1911
        $this->_par2Info->open($fileLocation);
1912
1913
        if ($this->_par2Info->error) {
1914
            return;
1915
        }
1916
        $releaseInfo = Release::query()->where('id', $this->_release->id)->select(['postdate', 'proc_pp'])->first();
1917
1918
        if ($releaseInfo === null) {
1919
            return;
1920
        }
1921
1922
        $postDate = Carbon::createFromFormat('Y-m-d H:i:s', $releaseInfo->postdate)->getTimestamp();
1923
1924
        // Only get a new name if the category is OTHER.
1925
        $foundName = true;
1926
        if ((int) $releaseInfo->proc_pp === 0 && config('nntmux.rename_par2') &&
1927
            \in_array(
1928
                (int) $this->_release->categories_id,
1929
                Category::OTHERS_GROUP,
1930
                false
1931
            )
1932
        ) {
1933
            $foundName = false;
1934
        }
1935
1936
        $filesAdded = 0;
1937
1938
        foreach ($this->_par2Info->getFileList() as $file) {
1939
            if (! isset($file['name'])) {
1940
                continue;
1941
            }
1942
1943
            // If we found a name and added 10 files, stop.
1944
            if ($foundName && $filesAdded > 10) {
1945
                break;
1946
            }
1947
1948
            // Add to release files.
1949
            if ($this->_addPAR2Files) {
1950
                if ($filesAdded < 11 && ReleaseFile::query()->where(['releases_id' => $this->_release->id, 'name' => $file['name']])->first() === null
1951
                ) {
1952
                    // Try to add the files to the DB.
1953
                    if (ReleaseFile::addReleaseFiles($this->_release->id, $file['name'], $file['size'], $postDate, 0, $file['hash_16K'])) {
1954
                        $filesAdded++;
1955
                    }
1956
                }
1957
            } else {
1958
                $filesAdded++;
1959
            }
1960
1961
            // Try to get a new name.
1962
            if (! $foundName) {
1963
                $this->_release->textstring = $file['name'];
1964
                $this->_release->releases_id = $this->_release->id;
1965
                if ($this->_nameFixer->checkName($this->_release, $this->_echoCLI, 'PAR2, ', 1, 1)) {
1966
                    $foundName = true;
1967
                }
1968
            }
1969
        }
1970
        // Update the file count with the new file count + old file count.
1971
        Release::query()->where('id', $this->_release->id)->increment('rarinnerfilecount', $filesAdded);
1972
        $this->_foundPAR2Info = true;
1973
    }
1974
1975
    /**
1976
     * @throws \Exception
1977
     */
1978
    protected function _processNfoFile($fileLocation): void
1979
    {
1980
        $data = @File::get($fileLocation);
1981
        if ($data != false && $this->_nfo->isNFO($data, $this->_release->guid) && $this->_nfo->addAlternateNfo($data, $this->_release, $this->_nntp)) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $data of type string to the boolean false. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
1982
            $this->_releaseHasNoNFO = false;
1983
        }
1984
    }
1985
1986
    /**
1987
     * @throws \Exception
1988
     */
1989
    protected function _processVideoFile($fileLocation): void
1990
    {
1991
        // Try to get a sample with it.
1992
        if (! $this->_foundSample) {
1993
            $this->_foundSample = $this->_getSample($fileLocation);
1994
        }
1995
1996
        /* Try to get a video with it.
1997
         * Don't get it here if _sampleMessageIDs is empty
1998
         * or has 1 message-id (Saves downloading another part).
1999
         */
2000
        if (! $this->_foundVideo && \count($this->_sampleMessageIDs) < 2) {
2001
            $this->_foundVideo = $this->_getVideo($fileLocation);
2002
        }
2003
2004
        // Try to get media info with it.
2005
        if (! $this->_foundMediaInfo) {
2006
            $this->_foundMediaInfo = $this->_getMediaInfo($fileLocation);
2007
        }
2008
    }
2009
2010
    /**
2011
     * Comparison function for uSort, for sorting NZB files.
2012
     */
2013
    protected function _sortNZB(array|string|null $a, array|string|null $b): int
2014
    {
2015
        $pos = 0;
2016
        $af = $bf = false;
2017
        $a = preg_replace('/\d+[ ._-]?(\/|\||[o0]f)[ ._-]?\d+?(?![ ._-]\d)/i', ' ', $a['title']);
2018
        $b = preg_replace('/\d+[ ._-]?(\/|\||[o0]f)[ ._-]?\d+?(?![ ._-]\d)/i', ' ', $b['title']);
2019
2020
        if (preg_match('/\.(part\d+|[r|z]\d+)(\s*\.rar)*($|[ ")\]-])/i', $a)) {
2021
            $af = true;
2022
        }
2023
        if (preg_match('/\.(part\d+|[r|z]\d+)(\s*\.rar)*($|[ ")\]-])/i', $b)) {
2024
            $bf = true;
2025
        }
2026
2027
        if (! $af && preg_match('/\.rar($|[ ")\]-])/i', $a)) {
2028
            $a = preg_replace('/\.rar(?:$|[ ")\]-])/i', '.*rar', $a);
2029
            $af = true;
2030
        }
2031
        if (! $bf && preg_match('/\.rar($|[ ")\]-])/i', $b)) {
2032
            $b = preg_replace('/\.rar(?:$|[ ")\]-])/i', '.*rar', $b);
2033
            $bf = true;
2034
        }
2035
2036
        if (! $af && ! $bf) {
2037
            return strnatcasecmp($a, $b);
2038
        }
2039
2040
        if (! $bf) {
2041
            return -1;
2042
        }
2043
2044
        if (! $af) {
2045
            return 1;
2046
        }
2047
2048
        if ($af && $bf) {
0 ignored issues
show
introduced by
The condition $bf is always true.
Loading history...
2049
            return strnatcasecmp($a, $b);
2050
        }
2051
2052
        if ($af) {
2053
            return -1;
2054
        }
2055
2056
        if ($bf) {
2057
            return 1;
2058
        }
2059
2060
        return $pos;
2061
    }
2062
2063
    /**
2064
     * Reset some variables for the current release.
2065
     */
2066
    protected function _resetReleaseStatus(): void
2067
    {
2068
        // Only process for samples, previews and images if not disabled.
2069
        $this->_foundVideo = ! $this->_processVideo;
2070
        $this->_foundMediaInfo = ! $this->_processMediaInfo;
2071
        $this->_foundAudioInfo = ! $this->_processAudioInfo;
2072
        $this->_foundAudioSample = ! $this->_processAudioSample;
2073
        $this->_foundJPGSample = ! $this->_processJPGSample;
2074
        $this->_foundSample = ! $this->_processThumbnails;
2075
        $this->_foundPAR2Info = false;
2076
2077
        $this->_passwordStatus = Releases::PASSWD_NONE;
2078
        $this->_releaseHasPassword = false;
2079
2080
        $this->_releaseGroupName = UsenetGroup::getNameByID($this->_release->groups_id);
2081
2082
        $this->_releaseHasNoNFO = false;
2083
        // Make sure we don't already have an nfo.
2084
        if ((int) $this->_release->nfostatus !== 1) {
2085
            $this->_releaseHasNoNFO = true;
2086
        }
2087
2088
        $this->_NZBHasCompressedFile = false;
2089
2090
        $this->_sampleMessageIDs = $this->_JPGMessageIDs = $this->_MediaInfoMessageIDs = [];
2091
        $this->_AudioInfoMessageIDs = $this->_RARFileMessageIDs = [];
2092
        $this->_AudioInfoExtension = '';
2093
2094
        $this->_addedFileInfo = 0;
2095
        $this->_totalFileInfo = 0;
2096
        $this->_compressedFilesChecked = 0;
2097
    }
2098
2099
    /**
2100
     * Echo a string to CLI.
2101
     *
2102
     * @param  string  $string  String to echo.
2103
     * @param  string  $type  Method type.
2104
     *
2105
     * @void
2106
     */
2107
    protected function _echo(string $string, string $type): void
2108
    {
2109
        if ($this->_echoCLI) {
2110
            (new ColorCLI())->$type($string);
2111
        }
2112
    }
2113
2114
    /**
2115
     * Echo a string to CLI. For debugging.
2116
     *
2117
     *
2118
     * @void
2119
     */
2120
    protected function _debug(string $string): void
2121
    {
2122
        $this->_echo('DEBUG: '.$string, 'debug');
2123
    }
2124
}
2125