@@ -167,153 +167,153 @@ public function handle()
167167 } elseif (isset ($ apiBook ['url_cover_image ' ]) && filter_var ($ apiBook ['url_cover_image ' ], FILTER_VALIDATE_URL )) {
168168 // Fallback to url_cover_image if it's a valid URL
169169 $ coverImage = $ apiBook ['url_cover_image ' ];
170- } elseif (isset ($ apiBook ['url_librivox ' ])) {
171- // Last resort: try to construct from url_librivox, removing potential double slashes
172- $ constructedUrl = rtrim ($ apiBook ['url_librivox ' ], '/ ' ) . '/cover_small.jpg ' ;
173- if (filter_var ($ constructedUrl , FILTER_VALIDATE_URL )) {
174- $ coverImage = $ constructedUrl ;
170+ } elseif (isset ($ apiBook ['url_librivox ' ])) {
171+ // Last resort: try to construct from url_librivox, removing potential double slashes
172+ $ constructedUrl = rtrim ($ apiBook ['url_librivox ' ], '/ ' ) . '/cover_small.jpg ' ;
173+ if (filter_var ($ constructedUrl , FILTER_VALIDATE_URL )) {
174+ $ coverImage = $ constructedUrl ;
175+ }
175176 }
176- }
177177
178- // Final fallback to a generic placeholder if no valid image URL is found
179- if (empty ($ coverImage ) || !filter_var ($ coverImage , FILTER_VALIDATE_URL )) {
180- $ coverImage = 'https://librivox.org/images/librivox_logo_small.png ' ; // Default placeholder
181- }
182-
183- // Main Audiobook Data
184- $ audiobookData = [
185- 'title ' => $ title ,
186- 'author ' => $ authorName ,
187- 'narrator ' => $ narratorName ,
188- 'description ' => $ description ,
189- 'cover_image ' => $ coverImage ,
190- 'duration ' => $ durationStr ,
191- 'source_url ' => null , // Main source_url is null, sections will have actual files
192- 'category_id ' => $ category ->id ,
193- 'language ' => $ fullLanguageName ,
194- 'librivox_url ' => $ apiBook ['url_librivox ' ] ?? null , // Direct LibriVox project page URL
195- 'librivox_id ' => $ librivoxId , // Use LibriVox project ID as librivox_id
196- ];
197-
198- // Generate a unique slug for the audiobook
199- $ baseSlug = Str::slug ($ title );
200- $ slug = $ baseSlug ;
201- $ counter = 1 ;
202-
203- // Check for slug uniqueness and append counter if necessary
204- while (Audiobook::where ('slug ' , $ slug )->where ('librivox_id ' , '!= ' , $ librivoxId )->exists ()) {
205- $ slug = $ baseSlug . '- ' . $ counter ++;
206- }
207- $ audiobookData ['slug ' ] = $ slug ;
208-
209- $ this ->info ("Processing book: {$ title } (LibriVox ID: {$ librivoxId }), Slug: {$ slug }" );
210- Log::info ("Data for updateOrCreate: " , $ audiobookData );
211-
212- $ book = null ;
213- if (!$ isDryRun ) {
214- // Create or Update the main Audiobook record
215- $ book = Audiobook::updateOrCreate (
216- ['librivox_id ' => $ librivoxId ],
217- $ audiobookData
218- );
219-
220- if ($ book ->wasRecentlyCreated ) {
221- $ createdCount ++;
222- $ this ->info ("Created audiobook: {$ book ->title } (Slug: {$ book ->slug }) " );
223- } elseif ($ book ->wasChanged ()) {
224- $ updatedCount ++;
225- $ this ->info ("Updated audiobook: {$ book ->title } (Slug: {$ book ->slug }) " );
226- } else {
227- $ this ->info ("Audiobook matched existing record, no changes: {$ book ->title } (Slug: {$ book ->slug }) " );
178+ // Final fallback to a generic placeholder if no valid image URL is found
179+ if (empty ($ coverImage ) || !filter_var ($ coverImage , FILTER_VALIDATE_URL )) {
180+ $ coverImage = 'https://librivox.org/images/librivox_logo_small.png ' ; // Default placeholder
228181 }
229- } else {
230- $ this ->info ("DRY RUN: Would create/update audiobook: {$ title } (Slug: {$ slug }) " );
231- $ book = (object )['id ' => 99999 , 'librivox_id ' => $ librivoxId , 'title ' => $ title , 'narrator ' => $ narratorName ]; // Mock book for dry run
232- }
233182
183+ // Main Audiobook Data
184+ $ audiobookData = [
185+ 'title ' => $ title ,
186+ 'author ' => $ authorName ,
187+ 'narrator ' => $ narratorName ,
188+ 'description ' => $ description ,
189+ 'cover_image ' => $ coverImage ,
190+ 'duration ' => $ durationStr ,
191+ 'source_url ' => null , // Main source_url is null, sections will have actual files
192+ 'category_id ' => $ category ->id ,
193+ 'language ' => $ fullLanguageName ,
194+ 'librivox_url ' => $ apiBook ['url_librivox ' ] ?? null , // Direct LibriVox project page URL
195+ 'librivox_id ' => $ librivoxId , // Use LibriVox project ID as librivox_id
196+ ];
197+
198+ // Generate a unique slug for the audiobook
199+ $ baseSlug = Str::slug ($ title );
200+ $ slug = $ baseSlug ;
201+ $ counter = 1 ;
202+
203+ // Check for slug uniqueness and append counter if necessary
204+ while (Audiobook::where ('slug ' , $ slug )->where ('librivox_id ' , '!= ' , $ librivoxId )->exists ()) {
205+ $ slug = $ baseSlug . '- ' . $ counter ++;
206+ }
207+ $ audiobookData ['slug ' ] = $ slug ;
234208
235- // --- Section Processing ---
236- // Fetch detailed track metadata for sections using the LibriVox audiotracks API
237- $ audioTracks = $ this ->libriVoxService ->fetchAudiobookTracks ($ librivoxId );
209+ $ this ->info ("Processing book: {$ title } (LibriVox ID: {$ librivoxId }), Slug: {$ slug }" );
210+ Log::info ("Data for updateOrCreate: " , $ audiobookData );
238211
239- if (! empty ( $ audioTracks )) {
212+ $ book = null ;
240213 if (!$ isDryRun ) {
241- // Clear existing sections for this audiobook to prevent duplicates/stale data
242- AudiobookSection::where ('audiobook_id ' , $ book ->id )->delete ();
243- $ this ->info ("Cleared existing sections for audiobook: {$ book ->title }" );
214+ // Create or Update the main Audiobook record
215+ $ book = Audiobook::updateOrCreate (
216+ ['librivox_id ' => $ librivoxId ],
217+ $ audiobookData
218+ );
219+
220+ if ($ book ->wasRecentlyCreated ) {
221+ $ createdCount ++;
222+ $ this ->info ("Created audiobook: {$ book ->title } (Slug: {$ book ->slug }) " );
223+ } elseif ($ book ->wasChanged ()) {
224+ $ updatedCount ++;
225+ $ this ->info ("Updated audiobook: {$ book ->title } (Slug: {$ book ->slug }) " );
226+ } else {
227+ $ this ->info ("Audiobook matched existing record, no changes: {$ book ->title } (Slug: {$ book ->slug }) " );
228+ }
244229 } else {
245- $ this ->info ("DRY RUN: Would clear existing sections for audiobook: {$ book ->title }" );
230+ $ this ->info ("DRY RUN: Would create/update audiobook: {$ title } (Slug: {$ slug }) " );
231+ $ book = (object )['id ' => 99999 , 'librivox_id ' => $ librivoxId , 'title ' => $ title , 'narrator ' => $ narratorName ]; // Mock book for dry run
246232 }
247233
248- $ sectionNumber = 1 ;
249- foreach ($ audioTracks as $ track ) {
250- try {
251- $ sectionTitle = $ track ['section_title ' ] ?? 'Part ' . $ sectionNumber ;
252- $ sourceUrl = $ track ['listen_url ' ] ?? null ; // Direct listen URL from LibriVox API
253- $ duration = $ track ['playtime ' ] ?? null ; // Playtime in H:M:S format
254234
255- if (empty ($ sourceUrl )) {
256- $ this ->warn ("Skipping section ' {$ sectionTitle }' for book ' {$ book ->title }' due to missing source URL. " );
257- continue ;
258- }
235+ // --- Section Processing ---
236+ // Fetch detailed track metadata for sections using the LibriVox audiotracks API
237+ $ audioTracks = $ this ->libriVoxService ->fetchAudiobookTracks ($ librivoxId );
238+
239+ if (!empty ($ audioTracks )) {
240+ if (!$ isDryRun ) {
241+ // Clear existing sections for this audiobook to prevent duplicates/stale data
242+ AudiobookSection::where ('audiobook_id ' , $ book ->id )->delete ();
243+ $ this ->info ("Cleared existing sections for audiobook: {$ book ->title }" );
244+ } else {
245+ $ this ->info ("DRY RUN: Would clear existing sections for audiobook: {$ book ->title }" );
246+ }
259247
260- if (!$ isDryRun ) {
261- AudiobookSection::create ([
262- 'audiobook_id ' => $ book ->id ,
263- 'title ' => $ sectionTitle ,
264- 'section_number ' => $ sectionNumber ,
265- 'source_url ' => $ sourceUrl ,
266- 'duration ' => $ duration ,
267- 'reader_name ' => $ track ['reader ' ] ?? $ book ->narrator , // Prefer section-specific reader, fallback to book narrator
248+ $ sectionNumber = 1 ;
249+ foreach ($ audioTracks as $ track ) {
250+ try {
251+ $ sectionTitle = $ track ['section_title ' ] ?? 'Part ' . $ sectionNumber ;
252+ $ sourceUrl = $ track ['listen_url ' ] ?? null ; // Direct listen URL from LibriVox API
253+ $ duration = $ track ['playtime ' ] ?? null ; // Playtime in H:M:S format
254+
255+ if (empty ($ sourceUrl )) {
256+ $ this ->warn ("Skipping section ' {$ sectionTitle }' for book ' {$ book ->title }' due to missing source URL. " );
257+ continue ;
258+ }
259+
260+ if (!$ isDryRun ) {
261+ AudiobookSection::create ([
262+ 'audiobook_id ' => $ book ->id ,
263+ 'title ' => $ sectionTitle ,
264+ 'section_number ' => $ sectionNumber ,
265+ 'source_url ' => $ sourceUrl ,
266+ 'duration ' => $ duration ,
267+ 'reader_name ' => $ track ['reader ' ] ?? $ book ->narrator , // Prefer section-specific reader, fallback to book narrator
268+ ]);
269+ } else {
270+ $ this ->info ("DRY RUN: Would create section ' {$ sectionTitle }' for audiobook: {$ book ->title }" );
271+ }
272+ $ sectionNumber ++;
273+ } catch (\Exception $ e ) {
274+ Log::error ("Failed to process section for book ID {$ librivoxId }, section number {$ sectionNumber }: " . $ e ->getMessage (), [
275+ 'exception ' => $ e ,
276+ 'track_data ' => $ track ,
268277 ]);
269- } else {
270- $ this ->info ("DRY RUN: Would create section ' {$ sectionTitle }' for audiobook: {$ book ->title }" );
278+ $ this ->error ("Error processing section for book ' {$ book ->title }'. See logs for details. " );
271279 }
272- $ sectionNumber ++;
273- } catch (\Exception $ e ) {
274- Log::error ("Failed to process section for book ID {$ librivoxId }, section number {$ sectionNumber }: " . $ e ->getMessage (), [
275- 'exception ' => $ e ,
276- 'track_data ' => $ track ,
277- ]);
278- $ this ->error ("Error processing section for book ' {$ book ->title }'. See logs for details. " );
279280 }
281+ $ this ->info ("Imported " . (count ($ audioTracks )) . " sections for audiobook: {$ book ->title }" );
282+ } else {
283+ $ this ->warn ("No audio tracks found for sections for book: {$ book ->title } (LibriVox ID: {$ librivoxId }). " );
280284 }
281- $ this ->info ("Imported " . (count ($ audioTracks )) . " sections for audiobook: {$ book ->title }" );
282- } else {
283- $ this ->warn ("No audio tracks found for sections for book: {$ book ->title } (LibriVox ID: {$ librivoxId }). " );
285+
286+ $ progressBar ->advance ();
287+ } catch (\Exception $ e ) {
288+ Log::error ("Failed to import audiobook with LibriVox ID {$ librivoxId }: " . $ e ->getMessage (), [
289+ 'exception ' => $ e ,
290+ 'api_book_data ' => $ apiBook ,
291+ ]);
292+ $ this ->error ("Error importing audiobook ' {$ title }'. See logs for details. Skipping to next book. " );
293+ $ progressBar ->advance (); // Ensure progress bar advances even on error
284294 }
295+ }
285296
286- $ progressBar ->advance ();
287- } catch (\Exception $ e ) {
288- Log::error ("Failed to import audiobook with LibriVox ID {$ librivoxId }: " . $ e ->getMessage (), [
289- 'exception ' => $ e ,
290- 'api_book_data ' => $ apiBook ,
291- ]);
292- $ this ->error ("Error importing audiobook ' {$ title }'. See logs for details. Skipping to next book. " );
293- $ progressBar ->advance (); // Ensure progress bar advances even on error
297+ $ progressBar ->finish ();
298+ $ this ->info ("\nProcessing complete. " );
299+ if ($ isDryRun ) {
300+ $ this ->info ("DRY RUN finished. No changes were made to the database. " );
301+ } else {
302+ $ this ->info ("{$ createdCount } audiobooks created, {$ updatedCount } audiobooks updated. " );
294303 }
295- }
296304
297- $ progressBar ->finish ();
298- $ this ->info ("\nProcessing complete. " );
299- if ($ isDryRun ) {
300- $ this ->info ("DRY RUN finished. No changes were made to the database. " );
301- } else {
302- $ this ->info ("{$ createdCount } audiobooks created, {$ updatedCount } audiobooks updated. " );
305+ return Command::SUCCESS ;
303306 }
304307
305- return Command::SUCCESS ;
306- }
307-
308- /**
309- * Converts ISO 639-2/3 language codes to full language names.
310- *
311- * @param string $code The ISO language code.
312- * @return string The full language name.
313- */
314- private function convertLanguageCodeToName (string $ code ): string
315- {
316- // Use the language mapping from the config file
317- return Config::get ('languages. ' . strtolower ($ code ), ucfirst (strtolower ($ code )));
308+ /**
309+ * Converts ISO 639-2/3 language codes to full language names.
310+ *
311+ * @param string $code The ISO language code.
312+ * @return string The full language name.
313+ */
314+ private function convertLanguageCodeToName (string $ code ): string
315+ {
316+ // Use the language mapping from the config file
317+ return Config::get ('languages. ' . strtolower ($ code ), ucfirst (strtolower ($ code )));
318+ }
318319 }
319- }
0 commit comments