Skip to content

Commit 4d06e53

Browse files
committed
feat: 设置 PV 按钮
1 parent ba12b7b commit 4d06e53

File tree

7 files changed

+187
-6
lines changed

7 files changed

+187
-6
lines changed

MaiChartManager/Controllers/MovieConvertController.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,18 @@ private static IConversion Concatenate(params IMediaInfo[] mediaInfos)
6868

6969
[HttpPut]
7070
[DisableRequestSizeLimit]
71-
public async Task SetMovie(int id, [FromForm] float padding, IFormFile file)
71+
public async Task SetMovie(int id, [FromForm] double padding, IFormFile file)
7272
{
73+
if (Path.GetExtension(file.FileName).Equals(".dat", StringComparison.InvariantCultureIgnoreCase))
74+
{
75+
var targetPath = Path.Combine(StaticSettings.StreamingAssets, settings.AssetDir, $@"MovieData\{id:000000}.dat");
76+
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
77+
await using var stream = System.IO.File.Open(targetPath, FileMode.Create);
78+
await file.CopyToAsync(stream);
79+
StaticSettings.MovieDataMap[id] = targetPath;
80+
return;
81+
}
82+
7383
if (IapManager.License != IapManager.LicenseStatus.Active) return;
7484
Response.Headers.Append("Content-Type", "text/event-stream");
7585
var tmpDir = Directory.CreateTempSubdirectory();
@@ -155,7 +165,7 @@ public async Task SetMovie(int id, [FromForm] float padding, IFormFile file)
155165
FileSystem.CopyFile(outputFile, targetPath, true);
156166

157167
StaticSettings.MovieDataMap[id] = targetPath;
158-
await Response.WriteAsync($"event: {SetMovieEventType.Success}\n\n");
168+
await Response.WriteAsync($"event: {SetMovieEventType.Success}\ndata: {SetMovieEventType.Success}\n\n");
159169
await Response.Body.FlushAsync();
160170
}
161171
catch (Exception e)

MaiChartManager/Front/src/components/ImportCreateChartButton/ImportChartButton/ImportStepDisplay.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default defineComponent({
2121
closable={false}
2222
maskClosable={false}
2323
closeOnEsc={false}
24-
v-model:show={show.value}
24+
show={show.value}
2525
>
2626
<NFlex vertical class="text-4">
2727
<div>

MaiChartManager/Front/src/components/ImportCreateChartButton/ImportChartButton/index.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,15 @@ export default defineComponent({
132132
fetchEventSource(`/MaiChartManagerServlet/SetMovieApi/${id}`, {
133133
method: 'PUT',
134134
body,
135-
onerror: reject,
135+
onerror() {
136+
reject();
137+
throw new Error("disable retry onerror");
138+
},
139+
onclose() {
140+
reject();
141+
throw new Error("disable retry onclose");
142+
},
143+
openWhenHidden: true,
136144
onmessage: (e) => {
137145
switch (e.event) {
138146
case 'Progress':

MaiChartManager/Front/src/components/ModManager/ConfigEditor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export default defineComponent({
6363
showAquaMaiInstallDone.value = true
6464
setTimeout(() => showAquaMaiInstallDone.value = false, 3000);
6565
} catch (e: any) {
66-
globalCapture(e, "安装 AquaMai 失败")
66+
globalCapture(e, "安装 AquaMai 失败,文件可能被占用了?")
6767
} finally {
6868
installingAquaMai.value = false
6969
}

MaiChartManager/Front/src/components/MusicEdit/AcbAwb.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import stdIcon from "@/assets/stdIcon.png";
88
import dxIcon from "@/assets/dxIcon.png";
99
import api from "@/client/api";
1010
import AudioPreviewEditorButton from "@/components/MusicEdit/AudioPreviewEditorButton";
11+
import SetMovieButton from "@/components/MusicEdit/SetMovieButton";
1112

1213
export default defineComponent({
1314
props: {
@@ -98,6 +99,7 @@ export default defineComponent({
9899
{props.song.isAcbAwbExist && <audio controls src={url.value} class="w-0 grow"/>}
99100
{selectedADir.value !== 'A000' && <NButton secondary class={`${!props.song.isAcbAwbExist && "w-full"}`} onClick={uploadFlow} loading={load.value}>{props.song.isAcbAwbExist ? '替换' : '设置'}音频</NButton>}
100101
{selectedADir.value !== 'A000' && props.song.isAcbAwbExist && <AudioPreviewEditorButton/>}
102+
{selectedADir.value !== 'A000' && props.song.isAcbAwbExist && <SetMovieButton song={props.song}/>}
101103

102104
{/* 打开文件对话框一般在左上角,所以在下边显示一个 Drawer */}
103105
<NDrawer v-model:show={tipShow.value} height={200} placement="bottom">
@@ -116,7 +118,7 @@ export default defineComponent({
116118
<NModal
117119
preset="card"
118120
class="w-[min(30vw,25em)]"
119-
title="设置偏移"
121+
title="设置偏移(秒)"
120122
v-model:show={setOffsetShow.value}
121123
>{{
122124
default: () => <NFlex vertical size="large">
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { defineComponent, PropType, ref } from "vue";
2+
import { NButton, NDrawer, NDrawerContent, NFlex, NInputNumber, NModal, NProgress, useDialog } from "naive-ui";
3+
import FileTypeIcon from "@/components/FileTypeIcon";
4+
import { LicenseStatus, MusicXmlWithABJacket } from "@/client/apiGen";
5+
import api from "@/client/api";
6+
import { globalCapture, showNeedPurchaseDialog, version } from "@/store/refs";
7+
import { fetchEventSource } from "@microsoft/fetch-event-source";
8+
9+
enum STEP {
10+
None,
11+
Select,
12+
Offset,
13+
Progress,
14+
}
15+
16+
export default defineComponent({
17+
props: {
18+
song: {type: Object as PropType<MusicXmlWithABJacket>, required: true},
19+
},
20+
setup(props) {
21+
const offset = ref(0)
22+
const load = ref(false)
23+
const okResolve = ref<Function>(() => {
24+
})
25+
const dialog = useDialog();
26+
const step = ref(STEP.None)
27+
const progress = ref(0)
28+
29+
const uploadMovie = (id: number, movie: File, offset: number) => new Promise<void>((resolve, reject) => {
30+
progress.value = 0;
31+
const body = new FormData();
32+
body.append('file', movie);
33+
body.append('offset', offset.toString());
34+
fetchEventSource(`/MaiChartManagerServlet/SetMovieApi/${id}`, {
35+
method: 'PUT',
36+
body,
37+
onerror() {
38+
reject();
39+
throw new Error("disable retry onerror");
40+
},
41+
onclose() {
42+
reject();
43+
throw new Error("disable retry onclose");
44+
},
45+
openWhenHidden: true,
46+
onmessage: (e) => {
47+
switch (e.event) {
48+
case 'Progress':
49+
progress.value = parseInt(e.data);
50+
break;
51+
case 'Success':
52+
console.log("success")
53+
resolve();
54+
break;
55+
case 'Error':
56+
reject(e.data);
57+
break;
58+
}
59+
}
60+
});
61+
})
62+
63+
const uploadFlow = async () => {
64+
step.value = STEP.Select
65+
try {
66+
const [fileHandle] = await window.showOpenFilePicker({
67+
id: 'movie',
68+
startIn: 'downloads',
69+
types: [
70+
{
71+
description: "支持的文件类型",
72+
accept: {
73+
"video/*": [".dat"],
74+
},
75+
},
76+
],
77+
});
78+
step.value = STEP.None
79+
if (!fileHandle) return;
80+
const file = await fileHandle.getFile() as File;
81+
82+
if (file.name.endsWith('.dat')) {
83+
load.value = true;
84+
await api.SetMovie(props.song.id!, {file, padding: 0});
85+
} else if (version.value?.license !== LicenseStatus.Active) {
86+
showNeedPurchaseDialog.value = true;
87+
} else {
88+
offset.value = 0;
89+
step.value = STEP.Offset
90+
await new Promise((resolve) => {
91+
okResolve.value = resolve;
92+
});
93+
load.value = true;
94+
progress.value = 0;
95+
step.value = STEP.Progress
96+
await uploadMovie(props.song.id!, file, offset.value);
97+
console.log("upload movie success")
98+
}
99+
} catch (e: any) {
100+
if (e.name === 'AbortError') return
101+
console.log(e)
102+
globalCapture(e, "导入音频出错")
103+
} finally {
104+
step.value = STEP.None
105+
load.value = false;
106+
}
107+
}
108+
109+
return () => <NButton secondary onClick={uploadFlow} loading={load.value}>
110+
设置 PV
111+
112+
<NDrawer show={step.value === STEP.Select} height={250} placement="bottom">
113+
<NDrawerContent title="可以选择的文件类型">
114+
<NFlex vertical>
115+
任何 FFmpeg 支持的视频格式(赞助版功能),或者已经自行转换好的 DAT 文件
116+
<div class="grid cols-4 justify-items-center text-8em gap-10">
117+
<FileTypeIcon type="MP4"/>
118+
<FileTypeIcon type="DAT"/>
119+
</div>
120+
</NFlex>
121+
</NDrawerContent>
122+
</NDrawer>
123+
<NModal
124+
preset="card"
125+
class="w-[min(30vw,25em)]"
126+
title="设置偏移(秒)"
127+
show={step.value === STEP.Offset}
128+
onUpdateShow={() => step.value = STEP.None}
129+
>{{
130+
default: () => <NFlex vertical size="large">
131+
<div>设为正数可以在视频前面添加黑场空白,设为负数则裁掉视频前面的一部分</div>
132+
<NInputNumber v-model:value={offset.value} class="w-full" step={0.01}/>
133+
</NFlex>,
134+
footer: () => <NFlex justify="end">
135+
<NButton onClick={okResolve.value as any}>确定</NButton>
136+
</NFlex>
137+
}}</NModal>
138+
<NModal
139+
preset="card"
140+
class="w-[min(40vw,40em)]"
141+
title="正在转换…"
142+
show={step.value === STEP.Progress}
143+
closable={false}
144+
maskClosable={false}
145+
closeOnEsc={false}
146+
>
147+
<NProgress
148+
type="line"
149+
status="success"
150+
percentage={progress.value}
151+
indicator-placement="inside"
152+
processing
153+
/>
154+
</NModal>
155+
</NButton>;
156+
}
157+
})

MaiChartManager/IapManager.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ public enum LicenseStatus
2121

2222
public static async Task Init()
2323
{
24+
# if DEBUG
25+
License = LicenseStatus.Active;
26+
return;
27+
# endif
2428
var license = await StoreContext.GetAppLicenseAsync();
2529
if (license is null)
2630
{

0 commit comments

Comments
 (0)