From 867134d56adb9b351914ca1fd14e67ebf975a747 Mon Sep 17 00:00:00 2001 From: Ben Hills Date: Thu, 20 Apr 2023 17:03:25 +0100 Subject: [PATCH] Improve support for PC2.0 chapters. Fix formatting. --- CHANGELOG.md | 4 +++ lib/src/model/attribute.dart | 3 +-- lib/src/model/item.dart | 17 +++++++++---- lib/src/model/language.dart | 8 +++--- lib/src/model/podcast.dart | 32 ++++++++++++++++++------ lib/src/model/search_result.dart | 15 ++++++++--- lib/src/search/base_search.dart | 3 ++- lib/src/search/itunes_search.dart | 24 ++++++++++++------ lib/src/search/podcast_index_search.dart | 25 ++++++++++++------ lib/src/utils/srt_parser.dart | 3 ++- lib/src/utils/utils.dart | 5 ++-- pubspec.yaml | 2 +- 12 files changed, 100 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a3d688..1596016 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.5 + +- Improve PC2.0 chapter support. + ## 0.5.4 - Add support for PC2.0 Transcripts (json/srt/subrip); diff --git a/lib/src/model/attribute.dart b/lib/src/model/attribute.dart index 34a6877..17f6465 100644 --- a/lib/src/model/attribute.dart +++ b/lib/src/model/attribute.dart @@ -16,7 +16,6 @@ enum Attribute { const Attribute({ required this.attribute, }); - + final String attribute; } - diff --git a/lib/src/model/item.dart b/lib/src/model/item.dart index 6bc7313..8098790 100644 --- a/lib/src/model/item.dart +++ b/lib/src/model/item.dart @@ -113,8 +113,12 @@ class Item { }); /// Takes our json map and builds a Podcast instance from it. - factory Item.fromJson({required Map? json, ResultType type = ResultType.itunes}) { - return type == ResultType.itunes ? _fromItunes(json!) : _fromPodcastIndex(json!); + factory Item.fromJson( + {required Map? json, + ResultType type = ResultType.itunes}) { + return type == ResultType.itunes + ? _fromItunes(json!) + : _fromPodcastIndex(json!); } static Item _fromItunes(Map json) { @@ -139,7 +143,8 @@ class Item { artworkUrl60: json['artworkUrl60'] as String?, artworkUrl100: json['artworkUrl100'] as String?, artworkUrl600: json['artworkUrl600'] as String?, - genre: Item._loadGenres(json['genreIds'].cast(), json['genres'].cast()), + genre: Item._loadGenres( + json['genreIds'].cast(), json['genres'].cast()), releaseDate: DateTime.parse(json['releaseDate']), country: json['country'] as String?, primaryGenreName: json['primaryGenreName'] as String?, @@ -148,14 +153,16 @@ class Item { } static Item _fromPodcastIndex(Map json) { - int pubDateSeconds = json['lastUpdateTime'] ?? DateTime.now().millisecondsSinceEpoch ~/ 1000; + int pubDateSeconds = + json['lastUpdateTime'] ?? DateTime.now().millisecondsSinceEpoch ~/ 1000; var pubDate = Duration(seconds: pubDateSeconds); var categories = json['categories']; var genres = []; if (categories != null) { - categories.forEach((key, value) => genres.add(Genre(int.parse(key), value))); + categories + .forEach((key, value) => genres.add(Genre(int.parse(key), value))); } return Item( diff --git a/lib/src/model/language.dart b/lib/src/model/language.dart index fa3ffc6..cbd9026 100644 --- a/lib/src/model/language.dart +++ b/lib/src/model/language.dart @@ -2,12 +2,14 @@ // MIT license that can be found in the LICENSE file. /// An Enum that provides a list of the languages the iTunes API currently supports. -enum Language{ +enum Language { none(code: ''), enUs(code: 'en_us'), jaJp(code: 'en_us'); - const Language({required this.code,}); + const Language({ + required this.code, + }); final String code; -} \ No newline at end of file +} diff --git a/lib/src/model/podcast.dart b/lib/src/model/podcast.dart index 3142ddc..fc4081b 100644 --- a/lib/src/model/podcast.dart +++ b/lib/src/model/podcast.dart @@ -2,6 +2,7 @@ // MIT license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:dart_rss/dart_rss.dart'; @@ -73,7 +74,8 @@ class Podcast { connectTimeout: timeout, receiveTimeout: timeout, headers: { - 'User-Agent': userAgent.isEmpty ? '$podcastSearchAgent' : '$userAgent', + 'User-Agent': + userAgent.isEmpty ? '$podcastSearchAgent' : '$userAgent', }, ), ); @@ -192,11 +194,14 @@ class Podcast { ), ); - if (episode.chapters!.chapters.isNotEmpty && !episode.chapters!.loaded && !forceReload) { + if (episode.chapters!.chapters.isNotEmpty && + !episode.chapters!.loaded && + !forceReload) { try { final response = await client.get(episode.chapters!.url); - if (response.statusCode == 200 && response.data is Map) { + if (response.statusCode == 200 && + response.data is Map) { _loadChapters(response, episode.chapters!); } } on DioError catch (e) { @@ -236,7 +241,8 @@ class Podcast { final jsonParser = JsonParser(); try { - final response = await client.get(transcriptUrl.url, options: Options(responseType: ResponseType.plain)); + final response = await client.get(transcriptUrl.url, + options: Options(responseType: ResponseType.plain)); /// What type of transcript are we loading here? if (transcriptUrl.type == TranscriptFormat.subrip) { @@ -285,8 +291,11 @@ class Podcast { try { final response = await client.get(url); - if (response.statusCode == 200 && response.data is Map) { + + if (response.statusCode == 200) { _loadChapters(response, chapters); + } else { + throw PodcastFailedException('Failed to download chapters file'); } } on DioError catch (e) { switch (e.type) { @@ -306,7 +315,14 @@ class Podcast { } static void _loadChapters(Response response, Chapters c) { - final data = Map.from(response.data); + Map data; + + if (response.data is String) { + data = jsonDecode(response.data); + } else { + data = Map.from(response.data); + } + final chapters = data['chapters'] ?? []; c.headers = ChapterHeaders( @@ -350,7 +366,9 @@ class Podcast { title: chapter['title'] ?? '', startTime: startTime ?? 0.0, endTime: endTime ?? 0.0, - toc: (chapter['toc'] != null && (chapter['toc'] as bool?) == false) ? false : true), + toc: (chapter['toc'] != null && (chapter['toc'] as bool?) == false) + ? false + : true), ); } } diff --git a/lib/src/model/search_result.dart b/lib/src/model/search_result.dart index 9bf6bde..c98db0e 100644 --- a/lib/src/model/search_result.dart +++ b/lib/src/model/search_result.dart @@ -10,6 +10,7 @@ enum ErrorType { connection, timeout, } + enum ResultType { itunes, podcastIndex, @@ -61,10 +62,13 @@ class SearchResult { processedTime = DateTime.now(), items = []; - factory SearchResult.fromJson({required dynamic json, ResultType type = ResultType.itunes}) { + factory SearchResult.fromJson( + {required dynamic json, ResultType type = ResultType.itunes}) { /// Did we get an error message? if (json['errorMessage'] != null) { - return SearchResult.fromError(lastError: json['errorMessage'] ?? '', lastErrorType: ErrorType.failed); + return SearchResult.fromError( + lastError: json['errorMessage'] ?? '', + lastErrorType: ErrorType.failed); } var dataStart = startTagMap[type]; @@ -73,10 +77,13 @@ class SearchResult { /// Fetch the results from the JSON data. final items = json[dataStart] == null ? null - : (json[dataStart] as List).cast>().map((Map item) { + : (json[dataStart] as List) + .cast>() + .map((Map item) { return Item.fromJson(json: item, type: type); }).toList(); - return SearchResult(resultCount: json[dataCount] ?? 0, items: items ?? []); + return SearchResult( + resultCount: json[dataCount] ?? 0, items: items ?? []); } } diff --git a/lib/src/search/base_search.dart b/lib/src/search/base_search.dart index 480ca07..83adcbc 100644 --- a/lib/src/search/base_search.dart +++ b/lib/src/search/base_search.dart @@ -1,7 +1,8 @@ import 'package:dio/dio.dart'; import 'package:podcast_search/podcast_search.dart'; -const podcastSearchAgent = 'podcast_search/0.4.0 https://github.com/amugofjava/anytime_podcast_player'; +const podcastSearchAgent = + 'podcast_search/0.4.0 https://github.com/amugofjava/anytime_podcast_player'; abstract class BaseSearch { /// Contains the type of error returning from the search. If no error occurred it diff --git a/lib/src/search/itunes_search.dart b/lib/src/search/itunes_search.dart index ee31705..50ad0d7 100644 --- a/lib/src/search/itunes_search.dart +++ b/lib/src/search/itunes_search.dart @@ -77,7 +77,9 @@ class ITunesSearch extends BaseSearch { connectTimeout: timeout, receiveTimeout: timeout, headers: { - 'User-Agent': userAgent == null || userAgent.isEmpty ? '$podcastSearchAgent' : '$userAgent', + 'User-Agent': userAgent == null || userAgent.isEmpty + ? '$podcastSearchAgent' + : '$userAgent', }, ), ); @@ -114,7 +116,8 @@ class ITunesSearch extends BaseSearch { setLastError(e); } - return SearchResult.fromError(lastError: lastError ?? '', lastErrorType: lastErrorType); + return SearchResult.fromError( + lastError: lastError ?? '', lastErrorType: lastErrorType); } /// Fetches the list of top podcasts @@ -150,7 +153,8 @@ class ITunesSearch extends BaseSearch { setLastError(e); } - return SearchResult.fromError(lastError: lastError ?? '', lastErrorType: lastErrorType); + return SearchResult.fromError( + lastError: lastError ?? '', lastErrorType: lastErrorType); } @override @@ -166,7 +170,8 @@ class ITunesSearch extends BaseSearch { for (var entry in entries) { var id = entry['id']['attributes']['im:id']; - final response = await _client.get(feedApiEndpoint + '/lookup?id=$id'); + final response = + await _client.get(feedApiEndpoint + '/lookup?id=$id'); final results = json.decode(response.data); if (results['results'] != null) { @@ -182,7 +187,8 @@ class ITunesSearch extends BaseSearch { setLastError(e); } - return SearchResult.fromError(lastError: lastError ?? '', lastErrorType: lastErrorType); + return SearchResult.fromError( + lastError: lastError ?? '', lastErrorType: lastErrorType); } /// This internal method constructs a correctly encoded URL which is then @@ -233,7 +239,9 @@ class ITunesSearch extends BaseSearch { } String _termParam() { - return term != null && term!.isNotEmpty ? '?term=' + Uri.encodeComponent(term!) : ''; + return term != null && term!.isNotEmpty + ? '?term=' + Uri.encodeComponent(term!) + : ''; } String _countryParam() { @@ -241,7 +249,9 @@ class ITunesSearch extends BaseSearch { } String _attributeParam() { - return _attribute != Attribute.none ? '&attribute=' + Uri.encodeComponent(_attribute!.attribute) : ''; + return _attribute != Attribute.none + ? '&attribute=' + Uri.encodeComponent(_attribute!.attribute) + : ''; } String _limitParam() { diff --git a/lib/src/search/podcast_index_search.dart b/lib/src/search/podcast_index_search.dart index 5fbcc45..622ae56 100644 --- a/lib/src/search/podcast_index_search.dart +++ b/lib/src/search/podcast_index_search.dart @@ -13,9 +13,12 @@ import 'package:podcast_search/src/search/base_search.dart'; /// that have been added before making a call to iTunes. The results are unpacked /// and stored as Item instances and wrapped in a SearchResult. class PodcastIndexSearch extends BaseSearch { - static String searchApiEndpoint = 'https://api.podcastindex.org/api/1.0/search'; - static String trendingApiEndpoint = 'https://api.podcastindex.org/api/1.0/podcasts/trending'; - static String genreApiEndpoint = 'https://api.podcastindex.org/api/1.0/categories/list'; + static String searchApiEndpoint = + 'https://api.podcastindex.org/api/1.0/search'; + static String trendingApiEndpoint = + 'https://api.podcastindex.org/api/1.0/podcasts/trending'; + static String genreApiEndpoint = + 'https://api.podcastindex.org/api/1.0/categories/list'; static const _genres = [ 'After-Shows', @@ -186,7 +189,9 @@ class PodcastIndexSearch extends BaseSearch { 'X-Auth-Date': newUnixTime, 'X-Auth-Key': podcastIndexProvider.key, 'Authorization': digest.toString(), - 'User-Agent': userAgent == null || userAgent!.isEmpty ? '$podcastSearchAgent' : '$userAgent', + 'User-Agent': userAgent == null || userAgent!.isEmpty + ? '$podcastSearchAgent' + : '$userAgent', }, ), ); @@ -213,12 +218,14 @@ class PodcastIndexSearch extends BaseSearch { try { var response = await _client.get(_buildSearchUrl(queryParams!)); - return SearchResult.fromJson(json: response.data, type: ResultType.podcastIndex); + return SearchResult.fromJson( + json: response.data, type: ResultType.podcastIndex); } on DioError catch (e) { setLastError(e); } - return SearchResult.fromError(lastError: _lastError ?? '', lastErrorType: _lastErrorType); + return SearchResult.fromError( + lastError: _lastError ?? '', lastErrorType: _lastErrorType); } /// Fetches the list of top podcasts @@ -245,12 +252,14 @@ class PodcastIndexSearch extends BaseSearch { 'max': limit, }..addAll(queryParams)); - return SearchResult.fromJson(json: response.data, type: ResultType.podcastIndex); + return SearchResult.fromJson( + json: response.data, type: ResultType.podcastIndex); } on DioError catch (e) { setLastError(e); } - return SearchResult.fromError(lastError: _lastError ?? '', lastErrorType: _lastErrorType); + return SearchResult.fromError( + lastError: _lastError ?? '', lastErrorType: _lastErrorType); } @override diff --git a/lib/src/utils/srt_parser.dart b/lib/src/utils/srt_parser.dart index 0b8bb28..6dbcdf3 100644 --- a/lib/src/utils/srt_parser.dart +++ b/lib/src/utils/srt_parser.dart @@ -49,7 +49,8 @@ class SrtParser { milliseconds: endTimeMilliseconds, ); - var subtitle = Subtitle(index: index, start: startTime, end: endTime, data: text ?? ''); + var subtitle = Subtitle( + index: index, start: startTime, end: endTime, data: text ?? ''); subtitles.add(subtitle); } diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 07e317e..a45c97a 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -196,7 +196,8 @@ class Utils { static const _allowablePatterns = { // Mon, 03 Jun 2019 10:00:00 PDT (type 1) - '([A-Za-z]{3}), ([0-9]{1,2}) ([A-Za-z]*) ([0-9]{4}) ([0-9][0-9]:[0-9][0-9]:[0-9][0-9]) ([A-Za-z]{3})': 1, + '([A-Za-z]{3}), ([0-9]{1,2}) ([A-Za-z]*) ([0-9]{4}) ([0-9][0-9]:[0-9][0-9]:[0-9][0-9]) ([A-Za-z]{3})': + 1, // Mon, 03 Jun 2019 10:00:00 +02:30 (type 2) '([A-Za-z]{3}), ([0-9]{1,2}) ([A-Za-z]*) ([0-9]{4}) ([0-9][0-9]:[0-9][0-9]:[0-9][0-9]) ([\+|\-][0-9][0-9]:[0-9][0-9])': @@ -233,7 +234,7 @@ class Utils { /// timezone code, so this will probably suffice for a large proportion of dates in that /// format. Hopefully, most pubDate values will use a UTC offset instead. static DateTime? parseRFC2822Date(String date) { - var result; + DateTime? result; _allowablePatterns.forEach((k, v) { final exp = RegExp(k); diff --git a/pubspec.yaml b/pubspec.yaml index 704b727..4493cb6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: podcast_search description: A library for searching for podcasts, parsing podcast RSS feeds and obtaining episodes details. Supports searching via iTunes and PodcastIndex (preview). -version: 0.5.4 +version: 0.5.5 homepage: https://github.com/amugofjava/podcast_search environment: