mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: super resolution from kazumi/main
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -26,6 +26,27 @@ class Constants {
|
||||
'%7B%22appId%22%3A5%2C%22platform%22%3A3%2C%22version%22%3A%221.46.2%22%2C%22abtest%22%3A%22%22%7D';
|
||||
//Uri.encodeComponent('{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}');
|
||||
|
||||
// 超分辨率滤镜
|
||||
static const List<String> mpvAnime4KShaders = [
|
||||
'Anime4K_Clamp_Highlights.glsl',
|
||||
'Anime4K_Restore_CNN_VL.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_VL.glsl',
|
||||
'Anime4K_AutoDownscalePre_x2.glsl',
|
||||
'Anime4K_AutoDownscalePre_x4.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_M.glsl'
|
||||
];
|
||||
|
||||
// 超分辨率滤镜 (轻量)
|
||||
static const List<String> mpvAnime4KShadersLite = [
|
||||
'Anime4K_Clamp_Highlights.glsl',
|
||||
'Anime4K_Restore_CNN_M.glsl',
|
||||
'Anime4K_Restore_CNN_S.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_M.glsl',
|
||||
'Anime4K_AutoDownscalePre_x2.glsl',
|
||||
'Anime4K_AutoDownscalePre_x4.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_S.glsl'
|
||||
];
|
||||
|
||||
//内容来自 https://passport.bilibili.com/web/generic/country/list
|
||||
static const List<Map<String, dynamic>> internationalDialingPrefix = [
|
||||
{"id": 1, "cname": "中国大陆", "country_id": "86"},
|
||||
|
||||
5
lib/models/common/super_resolution_type.dart
Normal file
5
lib/models/common/super_resolution_type.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
enum SuperResolutionType { disable, efficiency, quality }
|
||||
|
||||
extension SuperResolutionTypeExt on SuperResolutionType {
|
||||
String get title => ['禁用', '效率', '画质'][index];
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import 'package:PiliPlus/models/common/dynamics_type.dart';
|
||||
import 'package:PiliPlus/models/common/nav_bar_config.dart';
|
||||
import 'package:PiliPlus/models/common/rcmd_type.dart';
|
||||
import 'package:PiliPlus/models/common/reply_sort_type.dart';
|
||||
import 'package:PiliPlus/models/common/super_resolution_type.dart';
|
||||
import 'package:PiliPlus/models/common/theme_type.dart';
|
||||
import 'package:PiliPlus/models/common/up_panel_position.dart';
|
||||
import 'package:PiliPlus/models/video/play/CDN.dart';
|
||||
@@ -1860,6 +1861,32 @@ List<SettingsModel> get extraSettings => [
|
||||
}
|
||||
},
|
||||
),
|
||||
SettingsModel(
|
||||
settingsType: SettingsType.normal,
|
||||
title: '超分辨率',
|
||||
leading: const Icon(Icons.stay_current_landscape_outlined),
|
||||
getSubtitle: () =>
|
||||
'当前:「${SuperResolutionType.values[GStorage.superResolutionType].title}」\n超分辨率只对「番剧」启用, 需要启用硬件解码, 若启用硬件解码后仍然不生效, 尝试切换硬件解码器为 auto-copy',
|
||||
onTap: (setState) async {
|
||||
SuperResolutionType? result = await showDialog(
|
||||
context: Get.context!,
|
||||
builder: (context) {
|
||||
return SelectDialog<SuperResolutionType>(
|
||||
title: '超分辨率',
|
||||
value:
|
||||
SuperResolutionType.values[GStorage.superResolutionType],
|
||||
values: SuperResolutionType.values.map((e) {
|
||||
return {'title': e.title, 'value': e};
|
||||
}).toList());
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
await GStorage.setting
|
||||
.put(SettingBoxKey.superResolutionType, result.index);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
),
|
||||
SettingsModel(
|
||||
settingsType: SettingsType.sw1tch,
|
||||
enableFeedback: true,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/segment_progress_bar.dart';
|
||||
import 'package:PiliPlus/models/common/audio_normalization.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
@@ -9,6 +11,7 @@ import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:canvas_danmaku/canvas_danmaku.dart';
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -22,8 +25,10 @@ import 'package:PiliPlus/plugin/pl_player/models/play_repeat.dart';
|
||||
import 'package:PiliPlus/services/service_locator.dart';
|
||||
import 'package:PiliPlus/utils/feed_back.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:screen_brightness/screen_brightness.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
class PlPlayerController {
|
||||
Player? _videoPlayerController;
|
||||
@@ -540,6 +545,80 @@ class PlPlayerController {
|
||||
}
|
||||
}
|
||||
|
||||
Directory? shadersDirectory;
|
||||
Future<Directory?> copyShadersToExternalDirectory() async {
|
||||
if (shadersDirectory != null) {
|
||||
return shadersDirectory;
|
||||
}
|
||||
final manifestContent = await rootBundle.loadString('AssetManifest.json');
|
||||
final Map<String, dynamic> manifestMap = json.decode(manifestContent);
|
||||
final directory = await getApplicationSupportDirectory();
|
||||
shadersDirectory = Directory(path.join(directory.path, 'anime_shaders'));
|
||||
|
||||
if (!await shadersDirectory!.exists()) {
|
||||
await shadersDirectory!.create(recursive: true);
|
||||
}
|
||||
|
||||
final shaderFiles = manifestMap.keys.where((String key) =>
|
||||
key.startsWith('assets/shaders/') && key.endsWith('.glsl'));
|
||||
|
||||
// int copiedFilesCount = 0;
|
||||
|
||||
for (var filePath in shaderFiles) {
|
||||
final fileName = filePath.split('/').last;
|
||||
final targetFile = File(path.join(shadersDirectory!.path, fileName));
|
||||
if (await targetFile.exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
final data = await rootBundle.load(filePath);
|
||||
final List<int> bytes = data.buffer.asUint8List();
|
||||
await targetFile.writeAsBytes(bytes);
|
||||
// copiedFilesCount++;
|
||||
} catch (e) {
|
||||
debugPrint('$e');
|
||||
}
|
||||
}
|
||||
return shadersDirectory;
|
||||
}
|
||||
|
||||
late int superResolutionType = GStorage.superResolutionType;
|
||||
Future<void> setShader([int? type, NativePlayer? pp]) async {
|
||||
if (type == null) {
|
||||
type ??= superResolutionType;
|
||||
} else {
|
||||
superResolutionType = type;
|
||||
GStorage.setting.put(SettingBoxKey.superResolutionType, type);
|
||||
}
|
||||
pp ??= _videoPlayerController?.platform as NativePlayer;
|
||||
await pp.waitForPlayerInitialization;
|
||||
await pp.waitForVideoControllerInitializationIfAttached;
|
||||
if (type == 1) {
|
||||
await pp.command([
|
||||
'change-list',
|
||||
'glsl-shaders',
|
||||
'set',
|
||||
Utils.buildShadersAbsolutePath(
|
||||
(await copyShadersToExternalDirectory())?.path ?? '',
|
||||
Constants.mpvAnime4KShadersLite,
|
||||
),
|
||||
]);
|
||||
} else if (type == 2) {
|
||||
await pp.command([
|
||||
'change-list',
|
||||
'glsl-shaders',
|
||||
'set',
|
||||
Utils.buildShadersAbsolutePath(
|
||||
(await copyShadersToExternalDirectory())?.path ?? '',
|
||||
Constants.mpvAnime4KShaders,
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
await pp.command(['change-list', 'glsl-shaders', 'clr', '']);
|
||||
}
|
||||
}
|
||||
|
||||
// 配置播放器
|
||||
Future<Player> _createVideoController(
|
||||
DataSource dataSource,
|
||||
@@ -573,6 +652,9 @@ class PlPlayerController {
|
||||
);
|
||||
var pp = player.platform as NativePlayer;
|
||||
// 解除倍速限制
|
||||
if (Get.parameters['type'] == '1' || Get.parameters['type'] == '4') {
|
||||
setShader(superResolutionType, pp);
|
||||
}
|
||||
if (_videoPlayerController == null) {
|
||||
String audioNormalization = GStorage.audioNormalization;
|
||||
audioNormalization = switch (audioNormalization) {
|
||||
@@ -847,7 +929,7 @@ class PlPlayerController {
|
||||
SmartDialog.showToast('无法加载解码器, $event,可能会切换至软解');
|
||||
return;
|
||||
}
|
||||
// SmartDialog.showToast('视频加载错误, $event');
|
||||
SmartDialog.showToast('视频加载错误, $event');
|
||||
debugPrint('视频加载错误, $event');
|
||||
}),
|
||||
// videoPlayerController!.stream.volume.listen((event) {
|
||||
|
||||
@@ -11,4 +11,5 @@ enum BottomControlType {
|
||||
fullscreen,
|
||||
custom,
|
||||
viewPoints,
|
||||
superResolution,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/common/widgets/segment_progress_bar.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/common/super_resolution_type.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/introduction/controller.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
@@ -335,6 +336,44 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
/// 空白占位
|
||||
BottomControlType.space: const Spacer(),
|
||||
|
||||
/// 分段信息
|
||||
BottomControlType.superResolution: Get.parameters['type'] == '1' ||
|
||||
Get.parameters['type'] == '4'
|
||||
? Container(
|
||||
height: 30,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10),
|
||||
alignment: Alignment.center,
|
||||
child: PopupMenuButton<SuperResolutionType>(
|
||||
onSelected: (SuperResolutionType value) {
|
||||
plPlayerController.setShader(value.index);
|
||||
},
|
||||
initialValue: SuperResolutionType
|
||||
.values[plPlayerController.superResolutionType],
|
||||
color: Colors.black.withOpacity(0.8),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return SuperResolutionType.values
|
||||
.map((SuperResolutionType type) {
|
||||
return PopupMenuItem<SuperResolutionType>(
|
||||
height: 35,
|
||||
padding: const EdgeInsets.only(left: 30),
|
||||
value: type,
|
||||
child: Text(
|
||||
type.title,
|
||||
style:
|
||||
const TextStyle(color: Colors.white, fontSize: 13),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
child: Text(
|
||||
SuperResolutionType
|
||||
.values[plPlayerController.superResolutionType].title,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
|
||||
/// 分段信息
|
||||
BottomControlType.viewPoints: Obx(
|
||||
() => plPlayerController.viewPointList.isEmpty
|
||||
@@ -544,6 +583,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
if (anySeason) BottomControlType.pre,
|
||||
if (anySeason) BottomControlType.next,
|
||||
BottomControlType.space,
|
||||
BottomControlType.superResolution,
|
||||
BottomControlType.viewPoints,
|
||||
if (anySeason) BottomControlType.episode,
|
||||
if (isFullScreen) BottomControlType.fit,
|
||||
|
||||
@@ -37,16 +37,7 @@ class GStorage {
|
||||
static List<double> get speedList => List<double>.from(
|
||||
video.get(
|
||||
VideoBoxKey.speedsList,
|
||||
defaultValue: [
|
||||
0.5,
|
||||
0.75,
|
||||
1.0,
|
||||
1.25,
|
||||
1.5,
|
||||
1.75,
|
||||
2.0,
|
||||
3.0,
|
||||
],
|
||||
defaultValue: [0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 3.0],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -348,6 +339,9 @@ class GStorage {
|
||||
static String get audioNormalization =>
|
||||
GStorage.setting.get(SettingBoxKey.audioNormalization, defaultValue: '0');
|
||||
|
||||
static int get superResolutionType =>
|
||||
GStorage.setting.get(SettingBoxKey.superResolutionType, defaultValue: 0);
|
||||
|
||||
static List<double> get dynamicDetailRatio => List<double>.from(setting
|
||||
.get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0]));
|
||||
|
||||
@@ -570,6 +564,7 @@ class SettingBoxKey {
|
||||
mergeDanmaku = 'mergeDanmaku',
|
||||
showHotRcmd = 'showHotRcmd',
|
||||
audioNormalization = 'audioNormalization',
|
||||
superResolutionType = 'superResolutionType',
|
||||
|
||||
// Sponsor Block
|
||||
enableSponsorBlock = 'enableSponsorBlock',
|
||||
|
||||
@@ -41,12 +41,21 @@ import 'package:webview_cookie_manager/webview_cookie_manager.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web;
|
||||
import 'package:html/dom.dart' as dom;
|
||||
import 'package:html/parser.dart' as html_parser;
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
class Utils {
|
||||
static final Random random = Random();
|
||||
|
||||
static const channel = MethodChannel("PiliPlus");
|
||||
|
||||
static String buildShadersAbsolutePath(
|
||||
String baseDirectory, List<String> shaders) {
|
||||
List<String> absolutePaths = shaders.map((shader) {
|
||||
return path.join(baseDirectory, shader);
|
||||
}).toList();
|
||||
return absolutePaths.join(':');
|
||||
}
|
||||
|
||||
static void pushDynDetail(item, floor, {action = 'all'}) async {
|
||||
feedBack();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user