@@ -79,6 +79,7 @@ public function handle(TaskService $taskService, SubtitleScanner $subtitleScanne
7979 private function verifyFiles (TaskService $ taskService , SubtitleScanner $ subtitleScanner ) {
8080 $ metadataTransactions = [];
8181 $ subtitleTransactions = [];
82+ $ scannedDirectories = []; // TODO: this should really go in the indexer or a broken down part of the indexer
8283
8384 $ error = false ;
8485 $ index = 0 ;
@@ -89,43 +90,46 @@ private function verifyFiles(TaskService $taskService, SubtitleScanner $subtitle
8990
9091 try {
9192 foreach ($ this ->videos as $ video ) {
92- $ stored = []; // Metadata from db
93- $ changes = []; // Changes -> stored + changes . length has to be the same for every video so must generate defaults
93+ $ baseName = basename ($ video ->path );
94+ $ fileName = pathinfo ($ video ->path , PATHINFO_FILENAME );
95+ $ compositeId = $ video ->folder ->path . '/ ' . $ baseName ;
9496
95- $ compositeId = $ video ->folder ->path . '/ ' . basename ($ video ->path );
96- $ filePath = str_replace ('\\' , '/ ' , Storage::disk ('public ' )->path ('' )) . 'media/ ' . $ video ->folder ->path . '/ ' . basename ($ video ->path );
97+ // absolute file path in storage
98+ $ filePath = $ this ->getAbsoluteMediaPath ($ video );
99+ $ folderPath = dirname ($ filePath );
97100
98101 /**
99102 * @disregard P1013 Undefined method but it actually exists
100103 */
101- if (! Storage::disk ('public ' )->fileExists (' media/ ' . $ video ->folder ->path . ' / ' . basename ( $ video -> path ) )) {
102- throw new \Exception (' Video " media/' . $ video ->folder ->path . ' / ' . basename ( $ video -> path ) . ' " no longer exists. Index your videos before running this task again.' );
104+ if (! Storage::disk ('public ' )->fileExists (" media/ { $ video ->folder ->path } / { $ baseName }" )) {
105+ throw new \Exception (" File media/{ $ video ->folder ->path } / { $ baseName } no longer exists. Index your videos before running this task again." );
103106 }
104107
105- // For use with private storage -> $filePath = str_replace('\\', '/', Storage::path('app/private/')) . 'media/' . $video->folder->path . "/" . basename($video->path);
106- $ this ->fileMetaData = is_null ($ video ->uuid ) ? $ this ->getFileMetadata ($ filePath ) : []; // Empty unless uuid is missing or duration is missing
107- $ uuid = $ video ->uuid ?? '' ; // video has ? file has
108+ // enforce loading file metadata if uuid is missing
109+ $ this ->fileMetaData = is_null ($ video ->uuid ) ? $ this ->getFileMetadata ($ filePath, " uuid missing " ) : [];
110+ $ uuid = $ video ->uuid ?? '' ;
108111
109- // if the video in db or file does not have a valid uuid, it will add it in both the db and on the file.
110- if (! Uuid::isValid ($ uuid ?? '' )) {
111- if (! isset ($ this ->fileMetaData ['tags ' ]['uid ' ]) && ! isset ($ this ->fileMetaData ['tags ' ]['uuid ' ])) {
112- $ uuid = Str::uuid ()->toString ();
113- $ this ->embedChain [] = new EmbedUidInMetadata ($ filePath , $ uuid , $ this ->taskId , $ video ->id );
114- } else {
115- $ uuid = $ this ->fileMetaData ['tags ' ]['uuid ' ];
116- dump ("Found UUID {$ uuid }" );
117- $ video ->update (['uuid ' => $ uuid ]); // If embedding, video is updated in the embed job
118- }
112+ // handle missing or invalid Uuid
113+ if (! Uuid::isValid ($ uuid )) {
114+ $ uuid = $ this ->resolveMediaUuid ($ video , $ filePath );
119115 }
120116
121- $ metadata = Metadata::where ('uuid ' , $ uuid )->orWhere ('composite_id ' , $ compositeId )->first ();
117+ $ metadata = Metadata::firstOrCreate (
118+ ['uuid ' => $ uuid ],
119+ ['video_id ' => $ video ->id , 'composite_id ' => $ compositeId ]
120+ );
122121
123- if (! $ metadata ) {
124- $ metadata = Metadata::create (['uuid ' => $ uuid , 'composite_id ' => $ compositeId , 'video_id ' => $ video ->id ]);
125- }
122+ // $metadata = Metadata::where('uuid', $uuid)->orWhere('composite_id', $compositeId)->first();
126123
127- $ stored = $ metadata ->toArray ();
128- $ fileUpdated = ! is_null ($ metadata ->date_scanned ) && filemtime ($ filePath ) > strtotime ($ metadata ->date_scanned );
124+ // if (! $metadata) {
125+ // $metadata = Metadata::create(['uuid' => $uuid, 'composite_id' => $compositeId, 'video_id' => $video->id]);
126+ // }
127+
128+ $ stored = $ metadata ->toArray (); // Metadata from db
129+ $ changes = []; // Changes -> stored + changes . length has to be the same for every video so must generate defaults
130+
131+ $ lastScannedAt = is_null ($ metadata ->date_scanned ) ? 0 : strtotime ($ metadata ->date_scanned );
132+ $ fileUpdated = $ metadata ->date_scanned && filemtime ($ filePath ) > $ lastScannedAt ;
129133
130134 if (is_null ($ metadata ->uuid ) || $ fileUpdated ) {
131135 $ changes ['uuid ' ] = $ uuid ;
@@ -177,28 +181,69 @@ private function verifyFiles(TaskService $taskService, SubtitleScanner $subtitle
177181 preg_match ('!\d+! ' , $ episodeRaw [0 ] ?? '' , $ episode );
178182
179183 if (is_null ($ metadata ->duration ) || $ fileUpdated ) {
180- $ this ->confirmMetadata ($ filePath );
184+ $ this ->confirmMetadata ($ filePath, " Duration is missing or file was updated " );
181185 $ duration = $ this ->fileMetaData ['format ' ]['duration ' ] ?? $ this ->fileMetaData ['streams ' ][0 ]['duration ' ] ?? $ metadata ->duration ;
182186 $ changes ['duration ' ] = is_numeric ($ duration ) ? floor ($ duration ) : null ;
183187 }
184188
185- if (is_null ($ metadata ->subtitles_scanned_at ) || $ fileUpdated ) {
186- $ this ->confirmMetadata ($ filePath );
187- $ subtitleStreams = $ subtitleScanner ->extractSubtitleStreams ($ this ->fileMetaData );
189+ // Embedded Subtitles
190+
191+ $ subtitleScanNeeded = is_null ($ metadata ->subtitles_scanned_at ) || $ fileUpdated ;
192+
193+ if ($ subtitleScanNeeded ) {
194+ $ this ->confirmMetadata ($ filePath , "Subtitle scann date is missing or file was updated " );
195+ $ embeddedSubtitleTransactions = $ subtitleScanner ->scanEmbeddedSubtitles ($ uuid , $ this ->fileMetaData );
196+
197+ foreach ($ embeddedSubtitleTransactions as $ tx ) {
198+ $ subtitleTransactions [] = $ tx ;
199+ }
200+ }
201+
202+ // #region External Subtitles
203+
204+ // check if directory subtitle scan is needed
205+
206+ if (! isset ($ scannedDirectories [$ folderPath ])) {
207+ $ scannedDirectories [$ folderPath ] = [
208+ 'last_modified ' => filemtime ($ folderPath ),
209+ 'external_subtitles ' => null ,
210+ ];
211+ }
212+
213+ $ dirUpdated = $ scannedDirectories [$ folderPath ]['last_modified ' ] > $ lastScannedAt ;
214+
215+ // if directory updated, check directory for related subtitle files and make subtitle transactions for them with stream 0
216+
217+ // Scan each directory once per batch
218+ if (($ subtitleScanNeeded || $ dirUpdated ) && is_null ($ scannedDirectories [$ folderPath ]['external_subtitles ' ])) {
219+ $ scannedDirectories [$ folderPath ]['external_subtitles ' ] = $ subtitleScanner ->findExternalSubtitlesInDirectory ($ folderPath );
220+ }
221+
222+ // if subtitles in this directory have been scanned, check for matches
223+ if (! is_null ($ scannedDirectories [$ folderPath ]['external_subtitles ' ])) {
224+ $ relevantSubtitles = array_filter (
225+ $ scannedDirectories [$ folderPath ]['external_subtitles ' ],
226+ fn ($ sub ) => strtolower ($ sub ['media_filename ' ]) === strtolower ($ fileName )
227+ );
228+
229+ $ externalSubtitleTransactions = $ subtitleScanner ->buildSubtitleTransactions (
230+ $ uuid ,
231+ $ relevantSubtitles
232+ );
188233
189- if (count ($ subtitleStreams ) > 0 ) {
190- $ fileSubtitleTransactions = $ subtitleScanner ->buildSubtitleTransactions (
191- $ uuid ,
192- $ subtitleStreams
193- );
194- $ subtitleTransactions = array_merge ($ subtitleTransactions , $ fileSubtitleTransactions );
234+ foreach ($ externalSubtitleTransactions as $ tx ) {
235+ $ subtitleTransactions [] = $ tx ;
195236 }
237+ }
196238
239+ if ($ subtitleScanNeeded || $ dirUpdated ) {
197240 $ changes ['subtitles_scanned_at ' ] = now ();
198241 }
199242
243+ // #endregion
244+
200245 if (! $ is_audio && (is_null ($ metadata ->resolution_height ) || is_null ($ metadata ->codec ) || $ fileUpdated )) {
201- $ this ->confirmMetadata ($ filePath );
246+ $ this ->confirmMetadata ($ filePath, " Not audio and missing resolution or codec or because fileUpdated was { $ fileUpdated }" );
202247 foreach ($ this ->fileMetaData ['streams ' ] as $ stream ) {
203248 if (! isset ($ stream ['codec_type ' ]) || $ stream ['codec_type ' ] !== 'video ' || ! isset ($ stream ['width ' ])) {
204249 continue ;
@@ -349,7 +394,7 @@ private function verifyFiles(TaskService $taskService, SubtitleScanner $subtitle
349394 $ summary = 'Updated ' . count ($ metadataTransactions ) . ' videos from id ' . ($ metadataTransactions [0 ]['video_id ' ]) . ' to ' . ($ metadataTransactions [count ($ metadataTransactions ) - 1 ]['video_id ' ]);
350395
351396 if (count ($ subtitleTransactions ) > 0 ) {
352- Subtitle::upsert ($ subtitleTransactions , ['metadata_uuid ' , 'track_id ' ], ['language ' , 'codec ' ]);
397+ Subtitle::upsert ($ subtitleTransactions , ['metadata_uuid ' , 'source_key ' ], ['language ' , 'codec ' , ' is_default ' , ' is_forced ' , ' external_path ' ]);
353398 $ summary .= ' and found ' . count ($ subtitleTransactions ) . ' subtitle track(s) ' ;
354399 }
355400
@@ -360,13 +405,13 @@ private function verifyFiles(TaskService $taskService, SubtitleScanner $subtitle
360405 }
361406 }
362407
363- public static function getFileMetadata ($ filePath ) {
408+ public static function getFileMetadata ($ filePath, $ reason = " und " ) {
364409 try {
365410 // ? FFMPEG module with 6 test folders takes 35+ seconds but running the commands through shell takes 18 seconds
366411
367412 $ ext = pathinfo ($ filePath , PATHINFO_EXTENSION );
368413 if (config ('app.env ' ) === 'local ' ) {
369- dump ('PULLING METADATA ' . $ filePath );
414+ dump ('PULLING METADATA ' . $ filePath . ' reason: ' . $ reason );
370415 }
371416 $ command = [
372417 'ffprobe ' ,
@@ -423,14 +468,14 @@ public static function getFileMetadata($filePath) {
423468 }
424469
425470 // Call get metadata when needed
426- private function confirmMetadata ($ filePath ) {
471+ private function confirmMetadata (string $ filePath, string $ reason = ' und ' ) {
427472 if (is_null ($ this ->fileMetaData ) || count ($ this ->fileMetaData ) == 0 ) {
428- $ this ->fileMetaData = $ this ->getFileMetadata ($ filePath );
473+ $ this ->fileMetaData = $ this ->getFileMetadata ($ filePath, $ reason );
429474 }
430475 }
431476
432477 private function getAudioDescription ($ filePath ) {
433- $ this ->confirmMetadata ($ filePath );
478+ $ this ->confirmMetadata ($ filePath, " Get audio description " );
434479
435480 $ tags = $ this ->fileMetaData ['tags ' ] ?? [];
436481 $ streams = $ this ->fileMetaData ['streams ' ] ?? [];
@@ -560,4 +605,22 @@ protected function extractMimeType($filePath) {
560605
561606 return $ mimeType ;
562607 }
608+
609+ protected function getAbsoluteMediaPath ($ media ) {
610+ return str_replace ('\\' , '/ ' , Storage::disk ('public ' )->path ('' )) . "media/ {$ media ->folder ->path }/ " . basename ($ media ->path );
611+ }
612+
613+ protected function resolveMediaUuid ($ media , $ filePath ) {
614+ // if the media in db or file does not have a valid uuid, it will add it in both the db and on the file.
615+ if (! isset ($ this ->fileMetaData ['tags ' ]['uuid ' ])) {
616+ $ uuid = Str::uuid ()->toString ();
617+ $ this ->embedChain [] = new EmbedUidInMetadata ($ filePath , $ uuid , $ this ->taskId , $ media ->id );
618+ } else {
619+ $ uuid = $ this ->fileMetaData ['tags ' ]['uuid ' ];
620+ dump ("Found UUID on file {$ uuid }" );
621+ $ media ->update (['uuid ' => $ uuid ]); // If embedding, media file is updated in the embed job
622+ }
623+
624+ return $ uuid ;
625+ }
563626}
0 commit comments