feat: super resolution from kazumi/main

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-01-18 13:51:21 +08:00
parent 54cea9e5c7
commit 434a898c36
19 changed files with 3087 additions and 11 deletions

View File

@@ -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"},

View File

@@ -0,0 +1,5 @@
enum SuperResolutionType { disable, efficiency, quality }
extension SuperResolutionTypeExt on SuperResolutionType {
String get title => ['禁用', '效率', '画质'][index];
}

View File

@@ -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,

View File

@@ -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) {

View File

@@ -11,4 +11,5 @@ enum BottomControlType {
fullscreen,
custom,
viewPoints,
superResolution,
}

View File

@@ -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,

View File

@@ -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',

View File

@@ -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();