diff --git a/packages/backend/src/server/api/endpoints/notes/tts.ts b/packages/backend/src/server/api/endpoints/notes/tts.ts index 0238456947..2012c0e190 100644 --- a/packages/backend/src/server/api/endpoints/notes/tts.ts +++ b/packages/backend/src/server/api/endpoints/notes/tts.ts @@ -92,6 +92,7 @@ export default class extends Endpoint { // eslint- headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + instance.hfAuthKey, + Accept: 'audio/flac, */*', }, body: JSON.stringify({ inputs: note.text, @@ -99,18 +100,14 @@ export default class extends Endpoint { // eslint- timeout: 60000, }); - let contentType = res.headers.get('content-type') || 'application/octet-stream'; + let contentType = res.headers.get('Content-Type') || 'application/octet-stream'; - if (res.headers.get('content-type') === 'audio/flac') { - return { - body: res.body, - headers: { - 'Content-Type': contentType, - } - }; - } else { - throw new ApiError(meta.errors.unavailable); - } + if (contentType === 'audio/flac') { + return res.body; + } else { + throw new ApiError(meta.errors.unavailable); + } + }); } } diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 202c3998b2..67ebc44658 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -623,17 +623,16 @@ function emitUpdReaction(emoji: string, delta: number) { } watch(convert, (newBlob) => { - if (converturl.value && converturl.value.url) { - URL.revokeObjectURL(converturl.value.url); - } - - if (newBlob) { - converturl.value = { url: newBlob }; - } else { - converturl.value = null; - } + try { + if (newBlob) { + converturl.value = { url: newBlob }; + } else { + converturl.value = null; + } + } catch (error) { + console.error('Failed to create URL:', error); + } }); -console.log(converturl) onUnmounted(() => { if (converturl.value && converturl.value.url) { diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 157f20a279..8a160c8598 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -558,16 +558,6 @@ function loadConversation() { } watch(convert, (newBlob) => { - /* - try { - if (converturl.value && converturl.value.url) { - URL.revokeObjectURL(converturl.value.url); - } - } catch (error) { - console.error('Failed to revoke URL:', error); - } - */ - try { if (newBlob) { converturl.value = { url: newBlob }; @@ -577,7 +567,6 @@ watch(convert, (newBlob) => { } catch (error) { console.error('Failed to create URL:', error); } - }); onUnmounted(() => { diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index 1432efe97e..d8bfea8f69 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -302,27 +302,32 @@ export function getNoteMenu(props: { async function convert(): Promise { if (props.convert.value != null) return; props.converting.value = true; + const res = await misskeyApi('notes/tts', { noteId: appearNote.id, }, undefined, undefined, true); - const convertdata = await res.json(); - const contentType = convertdata.headers['Content-Type']; - - if (contentType?.startsWith('audio/')) { - console.log('Buffer:', convertdata.body._readableState.buffer[0].data); - - const buffers = new Uint8Array(convertdata.body._readableState.buffer[0].data).buffer; - - try { - const blob = new Blob([buffers], { type: contentType }); - props.convert.value = URL.createObjectURL(blob); - } catch (e) { - console.error('Failed to create Blob or Object URL:', e); + try { + if (res.body instanceof ReadableStream) { + const reader = res.body.getReader(); + const chunks: Uint8Array[] = []; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + chunks.push(value); + } + + const audioBlob = new Blob(chunks, { type: 'audio/flac' }); + + props.convert.value = URL.createObjectURL(audioBlob); + } else { + console.error('Response body is not a ReadableStream'); } - } else { - console.error('API did not return audio data.'); + } catch (e) { + console.error('Failed to create Blob or Object URL:', e); } + props.converting.value = false; } @@ -382,7 +387,7 @@ export function getNoteMenu(props: { if ($i.policies.canUseTTS && instance.ttsAvailable) { menuItems.push({ - icon: 'ti ti-headphone', + icon: 'ti ti-headphones', text: 'TTS', action: convert, }); diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index d39c6829ff..d1079769dd 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2773,7 +2773,7 @@ type NotesTranslateResponse = operations['notes___translate']['responses']['200' type NotesTTSRequest = operations['notes___tts']['requestBody']['content']['application/json']; // @public (undocumented) -type NotesTTSResponse = operations['notes___tts']['responses']['200']['content']['audio/flac']; +type NotesTTSResponse = unknown; // @public (undocumented) type NotesUnrenoteRequest = operations['notes___unrenote']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 354b9af078..e416df51e9 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -452,7 +452,7 @@ export type NotesTimelineResponse = operations['notes___timeline']['responses'][ export type NotesTranslateRequest = operations['notes___translate']['requestBody']['content']['application/json']; export type NotesTranslateResponse = operations['notes___translate']['responses']['200']['content']['application/json']; export type NotesTTSRequest = operations['notes___tts']['requestBody']['content']['application/json']; -export type NotesTTSResponse = operations['notes___tts']['responses']['200']['content']['audio/flac']; +export type NotesTTSResponse = unknown; export type NotesUnrenoteRequest = operations['notes___unrenote']['requestBody']['content']['application/json']; export type NotesUserListTimelineRequest = operations['notes___user-list-timeline']['requestBody']['content']['application/json']; export type NotesUserListTimelineResponse = operations['notes___user-list-timeline']['responses']['200']['content']['application/json'];