feat: add audio playback support (MP3/WAV/FLAC) and fix attachment loading in creator posts
This commit is contained in:
@@ -600,14 +600,47 @@ class KemonoProvider
|
||||
}
|
||||
|
||||
String _fileType(String value) {
|
||||
final lower = value.toLowerCase().split('?').first;
|
||||
if (lower.endsWith('.webm') || lower.endsWith('.mp4')) return 'video';
|
||||
final lower = value.toLowerCase().split('?').first.trim();
|
||||
if (lower.endsWith('.webm') ||
|
||||
lower.endsWith('.mp4') ||
|
||||
lower.endsWith('.mov') ||
|
||||
lower.endsWith('.mkv') ||
|
||||
lower.endsWith('.avi') ||
|
||||
lower.endsWith('.flv') ||
|
||||
lower.endsWith('.wmv') ||
|
||||
lower.endsWith('.m4v') ||
|
||||
lower.endsWith('.ts')) {
|
||||
return 'video';
|
||||
}
|
||||
if (lower.endsWith('.mp3') ||
|
||||
lower.endsWith('.m4a') ||
|
||||
lower.endsWith('.wav') ||
|
||||
lower.endsWith('.ogg') ||
|
||||
lower.endsWith('.flac') ||
|
||||
lower.endsWith('.aac') ||
|
||||
lower.endsWith('.opus') ||
|
||||
lower.endsWith('.wma')) {
|
||||
return 'audio';
|
||||
}
|
||||
if (lower.endsWith('.gif')) return 'gif';
|
||||
if (lower.endsWith('.swf')) return 'swf';
|
||||
if (lower.endsWith('.png')) return 'png';
|
||||
if (lower.endsWith('.jpg') ||
|
||||
if (lower.endsWith('.zip') ||
|
||||
lower.endsWith('.rar') ||
|
||||
lower.endsWith('.7z') ||
|
||||
lower.endsWith('.tar') ||
|
||||
lower.endsWith('.gz') ||
|
||||
lower.endsWith('.pdf') ||
|
||||
lower.endsWith('.txt')) {
|
||||
return 'archive';
|
||||
}
|
||||
if (lower.endsWith('.png') ||
|
||||
lower.endsWith('.jpg') ||
|
||||
lower.endsWith('.jpeg') ||
|
||||
lower.endsWith('.webp')) {
|
||||
lower.endsWith('.webp') ||
|
||||
lower.endsWith('.avif') ||
|
||||
lower.endsWith('.bmp') ||
|
||||
lower.endsWith('.heic') ||
|
||||
lower.endsWith('.tiff')) {
|
||||
return 'photo';
|
||||
}
|
||||
return 'unknown';
|
||||
|
||||
@@ -414,7 +414,15 @@ class PawchiveProvider
|
||||
}
|
||||
final attachments = json['attachments'];
|
||||
if (attachments is List) {
|
||||
files.addAll(attachments.whereType<Map>().map(Map<String, dynamic>.from));
|
||||
for (final att in attachments.whereType<Map>()) {
|
||||
final m = Map<String, dynamic>.from(att);
|
||||
final path = (m['path'] ?? '').toString();
|
||||
if (path.isNotEmpty &&
|
||||
files.any((existing) => (existing['path'] ?? '').toString() == path)) {
|
||||
continue;
|
||||
}
|
||||
files.add(m);
|
||||
}
|
||||
}
|
||||
|
||||
// Support link-only posts where author uploaded files to cloud drives (MEGA, GDrive, etc.)
|
||||
@@ -468,6 +476,17 @@ class PawchiveProvider
|
||||
];
|
||||
}
|
||||
|
||||
// Determine if there is a cover illustration for the post
|
||||
String? postCoverUrl;
|
||||
for (final f in files) {
|
||||
final p = (f['path'] ?? '').toString();
|
||||
final n = (f['name'] ?? '').toString();
|
||||
if (_isPhotoType('$n $p') && p.isNotEmpty) {
|
||||
postCoverUrl = _thumbnailUrl(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var index = 0;
|
||||
return files
|
||||
.map((fileMap) {
|
||||
@@ -475,18 +494,26 @@ class PawchiveProvider
|
||||
final fileName = (fileMap['name'] ?? '').toString();
|
||||
final previewOnly = fileMap['preview_only'] == true;
|
||||
|
||||
final previewUrl = _thumbnailUrl(rawPath);
|
||||
final type = _fileType('$fileName $rawPath');
|
||||
final isPhoto = _isPhotoType('$fileName $rawPath');
|
||||
|
||||
// img.pawchive.pw/thumbnail only generates thumbnails for images/gifs.
|
||||
// Non-image files (audio, video, archive) return 404 from thumbnail server.
|
||||
final previewUrl = isPhoto
|
||||
? _thumbnailUrl(rawPath)
|
||||
: (postCoverUrl ?? '');
|
||||
|
||||
final fileUrl = previewOnly
|
||||
? previewUrl
|
||||
? (isPhoto ? previewUrl : _fileDownloadUrl(rawPath, fileName))
|
||||
: _fileDownloadUrl(rawPath, fileName);
|
||||
|
||||
final type = _fileType('$fileName $rawPath');
|
||||
final stableId =
|
||||
'${query.service}:${query.artistId}:$postId:${index++}';
|
||||
final tags = [
|
||||
query.service,
|
||||
query.artistName,
|
||||
if (title.trim().isNotEmpty) title.trim(),
|
||||
if (type == 'audio') 'audio',
|
||||
];
|
||||
|
||||
return Post(
|
||||
@@ -600,24 +627,57 @@ class PawchiveProvider
|
||||
}
|
||||
|
||||
String _fileType(String value) {
|
||||
final lower = value.toLowerCase().split('?').first;
|
||||
if (lower.endsWith('.webm') || lower.endsWith('.mp4') || lower.endsWith('.mov')) {
|
||||
final lower = value.toLowerCase().split('?').first.trim();
|
||||
if (lower.endsWith('.webm') ||
|
||||
lower.endsWith('.mp4') ||
|
||||
lower.endsWith('.mov') ||
|
||||
lower.endsWith('.mkv') ||
|
||||
lower.endsWith('.avi') ||
|
||||
lower.endsWith('.flv') ||
|
||||
lower.endsWith('.wmv') ||
|
||||
lower.endsWith('.m4v') ||
|
||||
lower.endsWith('.ts')) {
|
||||
return 'video';
|
||||
}
|
||||
if (lower.endsWith('.mp3') ||
|
||||
lower.endsWith('.m4a') ||
|
||||
lower.endsWith('.wav') ||
|
||||
lower.endsWith('.ogg') ||
|
||||
lower.endsWith('.flac') ||
|
||||
lower.endsWith('.aac') ||
|
||||
lower.endsWith('.opus') ||
|
||||
lower.endsWith('.wma')) {
|
||||
return 'audio';
|
||||
}
|
||||
if (lower.endsWith('.gif')) return 'gif';
|
||||
if (lower.endsWith('.swf')) return 'swf';
|
||||
if (lower.endsWith('.zip') || lower.endsWith('.rar') || lower.endsWith('.7z')) {
|
||||
if (lower.endsWith('.zip') ||
|
||||
lower.endsWith('.rar') ||
|
||||
lower.endsWith('.7z') ||
|
||||
lower.endsWith('.tar') ||
|
||||
lower.endsWith('.gz') ||
|
||||
lower.endsWith('.pdf') ||
|
||||
lower.endsWith('.txt')) {
|
||||
return 'archive';
|
||||
}
|
||||
if (lower.endsWith('.png') ||
|
||||
lower.endsWith('.jpg') ||
|
||||
lower.endsWith('.jpeg') ||
|
||||
lower.endsWith('.webp')) {
|
||||
lower.endsWith('.webp') ||
|
||||
lower.endsWith('.avif') ||
|
||||
lower.endsWith('.bmp') ||
|
||||
lower.endsWith('.heic') ||
|
||||
lower.endsWith('.tiff')) {
|
||||
return 'photo';
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
bool _isPhotoType(String value) {
|
||||
final type = _fileType(value);
|
||||
return type == 'photo' || type == 'gif';
|
||||
}
|
||||
|
||||
DateTime? _dateFromAny(dynamic value) {
|
||||
if (value == null) return null;
|
||||
if (value is num) {
|
||||
|
||||
@@ -80,7 +80,16 @@ class MediaUrlSelector {
|
||||
]);
|
||||
}
|
||||
|
||||
static List<String> audio(Post post) {
|
||||
return _compact([
|
||||
post.fileUrl,
|
||||
if (_looksLikeAudioUrl(post.sampleUrl)) post.sampleUrl,
|
||||
if (_looksLikeAudioUrl(post.previewUrl)) post.previewUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
static bool isVideo(Post post) => _isVideo(post);
|
||||
static bool isAudio(Post post) => _isAudio(post);
|
||||
static bool isGif(Post post) => _isGif(post);
|
||||
|
||||
static List<String> _compact(Iterable<String> urls) {
|
||||
@@ -91,12 +100,32 @@ class MediaUrlSelector {
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
static bool _isAudio(Post post) {
|
||||
final value = '${post.fileType} ${post.fileUrl}'.toLowerCase();
|
||||
return value.contains('audio') ||
|
||||
value.contains('.mp3') ||
|
||||
value.contains('.m4a') ||
|
||||
value.contains('.wav') ||
|
||||
value.contains('.ogg') ||
|
||||
value.contains('.flac') ||
|
||||
value.contains('.aac') ||
|
||||
value.contains('.opus') ||
|
||||
value.contains('.wma');
|
||||
}
|
||||
|
||||
static bool _isVideo(Post post) {
|
||||
if (_isAudio(post)) return false;
|
||||
final value = '${post.fileType} ${post.fileUrl}'.toLowerCase();
|
||||
return value.contains('video') ||
|
||||
value.contains('.webm') ||
|
||||
value.contains('.mp4') ||
|
||||
value.contains('.mov');
|
||||
value.contains('.mov') ||
|
||||
value.contains('.mkv') ||
|
||||
value.contains('.avi') ||
|
||||
value.contains('.flv') ||
|
||||
value.contains('.wmv') ||
|
||||
value.contains('.m4v') ||
|
||||
value.contains('.ts');
|
||||
}
|
||||
|
||||
static bool _isGif(Post post) {
|
||||
@@ -108,6 +137,24 @@ class MediaUrlSelector {
|
||||
final value = url.toLowerCase();
|
||||
return value.contains('.webm') ||
|
||||
value.contains('.mp4') ||
|
||||
value.contains('.mov');
|
||||
value.contains('.mov') ||
|
||||
value.contains('.mkv') ||
|
||||
value.contains('.avi') ||
|
||||
value.contains('.flv') ||
|
||||
value.contains('.wmv') ||
|
||||
value.contains('.m4v') ||
|
||||
value.contains('.ts');
|
||||
}
|
||||
|
||||
static bool _looksLikeAudioUrl(String url) {
|
||||
final value = url.toLowerCase();
|
||||
return value.contains('.mp3') ||
|
||||
value.contains('.m4a') ||
|
||||
value.contains('.wav') ||
|
||||
value.contains('.ogg') ||
|
||||
value.contains('.flac') ||
|
||||
value.contains('.aac') ||
|
||||
value.contains('.opus') ||
|
||||
value.contains('.wma');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user