-
Notifications
You must be signed in to change notification settings - Fork 308
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ソング:labファイルを書き出す機能を追加 #2383
base: main
Are you sure you want to change the base?
ソング:labファイルを書き出す機能を追加 #2383
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
<template> | ||
<div v-if="nowAudioExporting" class="exporting-dialog"> | ||
<div> | ||
<QSpinner color="primary" size="2.5rem" /> | ||
<div class="q-mt-xs"> | ||
{{ nowRendering ? "レンダリング中・・・" : "音声を書き出し中・・・" }} | ||
</div> | ||
<QBtn | ||
v-if="nowRendering" | ||
padding="xs md" | ||
label="音声の書き出しをキャンセル" | ||
class="q-mt-sm" | ||
outline | ||
@click="cancelExport" | ||
/> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { computed } from "vue"; | ||
import { useStore } from "@/store"; | ||
|
||
const store = useStore(); | ||
|
||
const nowRendering = computed(() => { | ||
return store.state.nowRendering; | ||
}); | ||
const nowAudioExporting = computed(() => { | ||
return store.state.nowAudioExporting; | ||
}); | ||
|
||
const cancelExport = () => { | ||
void store.actions.CANCEL_AUDIO_EXPORT(); | ||
}; | ||
</script> | ||
|
||
<style scoped lang="scss"> | ||
@use "@/styles/v2/variables" as vars; | ||
@use "@/styles/colors" as colors; | ||
|
||
.exporting-dialog { | ||
background-color: rgba(colors.$display-rgb, 0.15); | ||
position: absolute; | ||
inset: 0; | ||
z-index: 10; | ||
display: flex; | ||
text-align: center; | ||
align-items: center; | ||
justify-content: center; | ||
|
||
> div { | ||
color: colors.$display; | ||
background: colors.$surface; | ||
border-radius: 6px; | ||
padding: 14px; | ||
} | ||
} | ||
</style> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<template> | ||
<div v-if="nowLabelExporting" class="exporting-dialog"> | ||
<div> | ||
<QSpinner color="primary" size="2.5rem" /> | ||
<div class="q-mt-xs"> | ||
{{ | ||
nowRendering | ||
? "レンダリング中・・・" | ||
: "labファイルを書き出し中・・・" | ||
}} | ||
</div> | ||
<QBtn | ||
v-if="nowRendering" | ||
padding="xs md" | ||
label="labファイルの書き出しをキャンセル" | ||
class="q-mt-sm" | ||
outline | ||
@click="cancelExport" | ||
/> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { computed } from "vue"; | ||
import { useStore } from "@/store"; | ||
|
||
const store = useStore(); | ||
|
||
const nowRendering = computed(() => { | ||
return store.state.nowRendering; | ||
}); | ||
const nowLabelExporting = computed(() => { | ||
return store.state.nowLabelExporting; | ||
}); | ||
|
||
const cancelExport = () => { | ||
void store.actions.CANCEL_LABEL_EXPORT(); | ||
}; | ||
</script> | ||
|
||
<style scoped lang="scss"> | ||
@use "@/styles/v2/variables" as vars; | ||
@use "@/styles/colors" as colors; | ||
|
||
.exporting-dialog { | ||
background-color: rgba(colors.$display-rgb, 0.15); | ||
position: absolute; | ||
inset: 0; | ||
z-index: 10; | ||
display: flex; | ||
text-align: center; | ||
align-items: center; | ||
justify-content: center; | ||
|
||
> div { | ||
color: colors.$display; | ||
background: colors.$surface; | ||
border-radius: 6px; | ||
padding: 14px; | ||
} | ||
} | ||
</style> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ import { computed } from "vue"; | |
import { useStore } from "@/store"; | ||
import { MenuItemData } from "@/components/Menu/type"; | ||
import { useRootMiscSetting } from "@/composables/useRootMiscSetting"; | ||
import { notifyResult } from "@/components/Dialog/Dialog"; | ||
|
||
export const useMenuBarData = () => { | ||
const store = useStore(); | ||
|
@@ -24,16 +25,38 @@ export const useMenuBarData = () => { | |
}); | ||
}; | ||
|
||
const exportLabelFile = async () => { | ||
const results = await store.actions.EXPORT_LABEL_FILES({}); | ||
|
||
if (results.length === 0) { | ||
throw new Error("results.length is 0."); | ||
} | ||
notifyResult( | ||
results[0], // TODO: SaveResultObject[] に対応する | ||
"text", | ||
store.actions, | ||
store.state.confirmedTips.notifyOnGenerate, | ||
); | ||
}; | ||
|
||
// 「ファイル」メニュー | ||
const fileSubMenuData = computed<MenuItemData[]>(() => [ | ||
{ | ||
type: "button", | ||
label: "音声を出力", | ||
label: "音声書き出し", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. トーク側と合わせました。 |
||
onClick: () => { | ||
void exportAudioFile(); | ||
}, | ||
disableWhenUiLocked: true, | ||
}, | ||
{ | ||
type: "button", | ||
label: "labファイルを書き出し", | ||
onClick: () => { | ||
void exportLabelFile(); | ||
}, | ||
disableWhenUiLocked: true, | ||
}, | ||
{ type: "separator" }, | ||
{ | ||
type: "button", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,8 @@ | ||
export const convertToWavFileData = (audioBuffer: AudioBuffer) => { | ||
import Encoding from "encoding-japanese"; | ||
import { Encoding as EncodingType } from "@/type/preload"; | ||
import { FramePhoneme } from "@/openapi"; | ||
|
||
export function generateWavFileData(audioBuffer: AudioBuffer) { | ||
const bytesPerSample = 4; // Float32 | ||
const formatCode = 3; // WAVE_FORMAT_IEEE_FLOAT | ||
|
||
|
@@ -53,4 +57,53 @@ export const convertToWavFileData = (audioBuffer: AudioBuffer) => { | |
} | ||
|
||
return new Uint8Array(buffer); | ||
}; | ||
} | ||
|
||
export async function generateTextFileData(obj: { | ||
text: string; | ||
encoding?: EncodingType; | ||
}) { | ||
obj.encoding ??= "UTF-8"; | ||
|
||
const textBlob = { | ||
"UTF-8": (text: string) => { | ||
const bom = new Uint8Array([0xef, 0xbb, 0xbf]); | ||
return new Blob([bom, text], { | ||
type: "text/plain;charset=UTF-8", | ||
}); | ||
}, | ||
Shift_JIS: (text: string) => { | ||
const sjisArray = Encoding.convert(Encoding.stringToCode(text), { | ||
to: "SJIS", | ||
type: "arraybuffer", | ||
}); | ||
return new Blob([new Uint8Array(sjisArray)], { | ||
type: "text/plain;charset=Shift_JIS", | ||
}); | ||
}, | ||
}[obj.encoding](obj.text); | ||
|
||
return await textBlob.arrayBuffer(); | ||
} | ||
|
||
export async function generateLabelFileData( | ||
phonemes: FramePhoneme[], | ||
frameRate: number, | ||
) { | ||
let labString = ""; | ||
let timestamp = 0; | ||
|
||
const writeLine = (phonemeLengthSeconds: number, phoneme: string) => { | ||
labString += timestamp.toFixed() + " "; | ||
timestamp += phonemeLengthSeconds * 10e7; // 100ns単位に変換 | ||
labString += timestamp.toFixed() + " "; | ||
labString += phoneme + "\n"; | ||
}; | ||
|
||
for (const phoneme of phonemes) { | ||
// REVIEW: vowel != "N" のときに vowel.toLowerCase() する必要がある…? | ||
writeLine(phoneme.frameLength / frameRate, phoneme.phoneme); | ||
} | ||
Comment on lines
+104
to
+105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. トークは There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. うーーーーーーん!!! 知らなかった・・・!!! ユーザー的にはどちらでも良さそう。 もしトーク側が未実装だったら、大文字のままにした方が良いかもですね! |
||
|
||
return await generateTextFileData({ text: labString }); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
メッセージの部分だけ差し替えられるようにすれば共通化できそう?
Container/PresentationのContainerが音声用とlab用とで2個ある形でも、SingEditorから差し込む形でも。