From d134431f8e0d6be704a33c260dafe18e7fde23e9 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Tue, 23 Sep 2025 17:27:33 +0900
Subject: [PATCH 1/6] wip
---
locales/index.d.ts | 4 +
locales/ja-JP.yml | 1 +
packages/frontend/package.json | 1 +
.../src/components/MkUploaderItems.vue | 13 ++-
.../frontend/src/composables/use-uploader.ts | 80 +++++++++++++++++--
pnpm-lock.yaml | 80 ++++++++++++++++++-
6 files changed, 167 insertions(+), 12 deletions(-)
diff --git a/locales/index.d.ts b/locales/index.d.ts
index 9bef0113a2..4434848635 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -1030,6 +1030,10 @@ export interface Locale extends ILocale {
* 処理中
*/
"processing": string;
+ /**
+ * 準備中
+ */
+ "preprocessing": string;
/**
* プレビュー
*/
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index b0d864ade8..0a6d020dd6 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -253,6 +253,7 @@ noteDeleteConfirm: "このノートを削除しますか?"
pinLimitExceeded: "これ以上ピン留めできません"
done: "完了"
processing: "処理中"
+preprocessing: "準備中"
preview: "プレビュー"
default: "デフォルト"
defaultValueIs: "デフォルト: {value}"
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index bacdc7b133..104ec42a18 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -57,6 +57,7 @@
"json5": "2.2.3",
"magic-string": "0.30.18",
"matter-js": "0.20.0",
+ "mediabunny": "1.15.1",
"mfm-js": "0.25.0",
"misskey-bubble-game": "workspace:*",
"misskey-js": "workspace:*",
diff --git a/packages/frontend/src/components/MkUploaderItems.vue b/packages/frontend/src/components/MkUploaderItems.vue
index f1370965c4..f31c717ad5 100644
--- a/packages/frontend/src/components/MkUploaderItems.vue
+++ b/packages/frontend/src/components/MkUploaderItems.vue
@@ -10,7 +10,10 @@ SPDX-License-Identifier: AGPL-3.0-only
:key="item.id"
v-panel
:class="[$style.item, { [$style.itemWaiting]: item.preprocessing, [$style.itemCompleted]: item.uploaded, [$style.itemFailed]: item.uploadFailed }]"
- :style="{ '--p': item.progress != null ? `${item.progress.value / item.progress.max * 100}%` : '0%' }"
+ :style="{
+ '--p': item.progress != null ? `${item.progress.value / item.progress.max * 100}%` : '0%',
+ '--pp': item.preprocessProgress != null ? `${item.preprocessProgress * 100}%` : '100%',
+ }"
@contextmenu.prevent.stop="onContextmenu(item, $event)"
>
@@ -19,11 +22,15 @@ SPDX-License-Identifier: AGPL-3.0-only
-
{{ item.name }}
+
+
+ {{ item.name }}
+
{{ item.file.type }}
({{ i18n.tsx._uploader.compressedToX({ x: bytes(item.compressedSize) }) }} = {{ i18n.tsx._uploader.savedXPercent({ x: Math.round((1 - item.compressedSize / item.file.size) * 100) }) }})
{{ bytes(item.file.size) }}
+ {{ i18n.ts.preprocessing }}
@@ -97,7 +104,7 @@ function onThumbnailClick(item: UploaderItem, ev: MouseEvent) {
position: absolute;
top: 0;
left: 0;
- width: 100%;
+ width: var(--pp, 100%);
height: 100%;
background: linear-gradient(-45deg, transparent 25%, var(--c) 25%,var(--c) 50%, transparent 50%, transparent 75%, var(--c) 75%, var(--c));
background-size: 25px 25px;
diff --git a/packages/frontend/src/composables/use-uploader.ts b/packages/frontend/src/composables/use-uploader.ts
index 826d8c5203..092b0ee6d1 100644
--- a/packages/frontend/src/composables/use-uploader.ts
+++ b/packages/frontend/src/composables/use-uploader.ts
@@ -43,6 +43,12 @@ const IMAGE_EDITING_SUPPORTED_TYPES = [
'image/webp',
];
+const VIDEO_COMPRESSION_SUPPORTED_TYPES = [ // TODO
+ 'video/mp4',
+ 'video/quicktime',
+ 'video/x-matroska',
+];
+
const WATERMARK_SUPPORTED_TYPES = IMAGE_EDITING_SUPPORTED_TYPES;
const IMAGE_PREPROCESS_NEEDED_TYPES = [
@@ -51,6 +57,10 @@ const IMAGE_PREPROCESS_NEEDED_TYPES = [
...IMAGE_EDITING_SUPPORTED_TYPES,
];
+const VIDEO_PREPROCESS_NEEDED_TYPES = [
+ ...VIDEO_COMPRESSION_SUPPORTED_TYPES,
+];
+
const mimeTypeMap = {
'image/webp': 'webp',
'image/jpeg': 'jpg',
@@ -64,6 +74,7 @@ export type UploaderItem = {
progress: { max: number; value: number } | null;
thumbnail: string | null;
preprocessing: boolean;
+ preprocessProgress: number | null;
uploading: boolean;
uploaded: Misskey.entities.DriveFile | null;
uploadFailed: boolean;
@@ -129,6 +140,7 @@ export function useUploader(options: {
progress: null,
thumbnail: THUMBNAIL_SUPPORTED_TYPES.includes(file.type) ? window.URL.createObjectURL(file) : null,
preprocessing: false,
+ preprocessProgress: null,
uploading: false,
aborted: false,
uploaded: null,
@@ -485,14 +497,24 @@ export function useUploader(options: {
async function preprocess(item: UploaderItem): Promise
{
item.preprocessing = true;
- try {
- if (IMAGE_PREPROCESS_NEEDED_TYPES.includes(item.file.type)) {
+ if (IMAGE_PREPROCESS_NEEDED_TYPES.includes(item.file.type)) {
+ try {
await preprocessForImage(item);
- }
- } catch (err) {
- console.error('Failed to preprocess image', err);
+ } catch (err) {
+ console.error('Failed to preprocess image', err);
// nop
+ }
+ }
+
+ if (VIDEO_PREPROCESS_NEEDED_TYPES.includes(item.file.type)) {
+ try {
+ await preprocessForVideo(item);
+ } catch (err) {
+ console.error('Failed to preprocess video', err);
+
+ // nop
+ }
}
item.preprocessing = false;
@@ -564,6 +586,54 @@ export function useUploader(options: {
item.preprocessedFile = markRaw(preprocessedFile);
}
+ async function preprocessForVideo(item: UploaderItem): Promise {
+ let preprocessedFile: Blob | File = item.file;
+
+ const needsCompress = true && VIDEO_COMPRESSION_SUPPORTED_TYPES.includes(preprocessedFile.type);
+
+ if (needsCompress) {
+ const mediabunny = await import('mediabunny');
+
+ const source = new mediabunny.BlobSource(preprocessedFile);
+
+ const input = new mediabunny.Input({
+ source,
+ formats: mediabunny.ALL_FORMATS,
+ });
+
+ const output = new mediabunny.Output({
+ target: new mediabunny.BufferTarget(),
+ format: new mediabunny.Mp4OutputFormat(),
+ });
+
+ const currentConversion = await mediabunny.Conversion.init({
+ input,
+ output,
+ video: {
+ //width: 320, // Height will be deduced automatically to retain aspect ratio
+ bitrate: mediabunny.QUALITY_MEDIUM,
+ },
+ audio: {
+ bitrate: 32e3,
+ },
+ });
+
+ currentConversion.onProgress = newProgress => item.preprocessProgress = newProgress;
+
+ await currentConversion.execute();
+
+ preprocessedFile = new Blob([output.target.buffer!], { type: output.format.mimeType });
+ item.compressedSize = output.target.buffer!.byteLength;
+ } else {
+ item.compressedSize = null;
+ item.uploadName = item.name;
+ }
+
+ if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
+ item.thumbnail = THUMBNAIL_SUPPORTED_TYPES.includes(preprocessedFile.type) ? window.URL.createObjectURL(preprocessedFile) : null;
+ item.preprocessedFile = markRaw(preprocessedFile);
+ }
+
onUnmounted(() => {
for (const item of items.value) {
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1b603a2ec3..9060fee7c9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -829,6 +829,9 @@ importers:
matter-js:
specifier: 0.20.0
version: 0.20.0
+ mediabunny:
+ specifier: 1.15.1
+ version: 1.15.1
mfm-js:
specifier: 0.25.0
version: 0.25.0
@@ -2440,138 +2443,163 @@ packages:
resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@img/sharp-libvips-linux-arm64@1.1.0':
resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@img/sharp-libvips-linux-arm@1.0.5':
resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
cpu: [arm]
os: [linux]
+ libc: [glibc]
'@img/sharp-libvips-linux-arm@1.1.0':
resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==}
cpu: [arm]
os: [linux]
+ libc: [glibc]
'@img/sharp-libvips-linux-ppc64@1.1.0':
resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==}
cpu: [ppc64]
os: [linux]
+ libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.0.4':
resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
cpu: [s390x]
os: [linux]
+ libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.1.0':
resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==}
cpu: [s390x]
os: [linux]
+ libc: [glibc]
'@img/sharp-libvips-linux-x64@1.0.4':
resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@img/sharp-libvips-linux-x64@1.1.0':
resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@img/sharp-libvips-linuxmusl-arm64@1.1.0':
resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.1.0':
resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@img/sharp-linux-arm64@0.33.5':
resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@img/sharp-linux-arm64@0.34.2':
resolution: {integrity: sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@img/sharp-linux-arm@0.33.5':
resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
+ libc: [glibc]
'@img/sharp-linux-arm@0.34.2':
resolution: {integrity: sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
+ libc: [glibc]
'@img/sharp-linux-s390x@0.33.5':
resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
+ libc: [glibc]
'@img/sharp-linux-s390x@0.34.2':
resolution: {integrity: sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
+ libc: [glibc]
'@img/sharp-linux-x64@0.33.5':
resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@img/sharp-linux-x64@0.34.2':
resolution: {integrity: sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.33.5':
resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@img/sharp-linuxmusl-arm64@0.34.2':
resolution: {integrity: sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@img/sharp-linuxmusl-x64@0.33.5':
resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@img/sharp-linuxmusl-x64@0.34.2':
resolution: {integrity: sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@img/sharp-wasm32@0.33.5':
resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
@@ -3276,36 +3304,42 @@ packages:
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
+ libc: [glibc]
'@parcel/watcher-linux-arm-musl@2.5.0':
resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
+ libc: [musl]
'@parcel/watcher-linux-arm64-glibc@2.5.0':
resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@parcel/watcher-linux-arm64-musl@2.5.0':
resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@parcel/watcher-linux-x64-glibc@2.5.0':
resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@parcel/watcher-linux-x64-musl@2.5.0':
resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@parcel/watcher-win32-arm64@2.5.0':
resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==}
@@ -3475,6 +3509,7 @@ packages:
resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==}
cpu: [arm]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-arm-gnueabihf@4.50.1':
resolution: {integrity: sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==}
@@ -3486,6 +3521,7 @@ packages:
resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==}
cpu: [arm]
os: [linux]
+ libc: [musl]
'@rollup/rollup-linux-arm-musleabihf@4.50.1':
resolution: {integrity: sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==}
@@ -3497,6 +3533,7 @@ packages:
resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-arm64-gnu@4.50.1':
resolution: {integrity: sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==}
@@ -3508,6 +3545,7 @@ packages:
resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@rollup/rollup-linux-arm64-musl@4.50.1':
resolution: {integrity: sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==}
@@ -3519,6 +3557,7 @@ packages:
resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==}
cpu: [loong64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-loongarch64-gnu@4.50.1':
resolution: {integrity: sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==}
@@ -3530,6 +3569,7 @@ packages:
resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==}
cpu: [ppc64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-ppc64-gnu@4.50.1':
resolution: {integrity: sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==}
@@ -3541,6 +3581,7 @@ packages:
resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==}
cpu: [riscv64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.50.1':
resolution: {integrity: sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==}
@@ -3564,6 +3605,7 @@ packages:
resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==}
cpu: [s390x]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-s390x-gnu@4.50.1':
resolution: {integrity: sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==}
@@ -3575,6 +3617,7 @@ packages:
resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.50.1':
resolution: {integrity: sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==}
@@ -3586,6 +3629,7 @@ packages:
resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@rollup/rollup-linux-x64-musl@4.50.1':
resolution: {integrity: sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==}
@@ -4325,24 +4369,28 @@ packages:
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@swc/core-linux-arm64-musl@1.13.5':
resolution: {integrity: sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@swc/core-linux-x64-gnu@1.13.5':
resolution: {integrity: sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@swc/core-linux-x64-musl@1.13.5':
resolution: {integrity: sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@swc/core-win32-arm64-msvc@1.13.5':
resolution: {integrity: sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==}
@@ -4566,6 +4614,12 @@ packages:
'@types/doctrine@0.0.9':
resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==}
+ '@types/dom-mediacapture-transform@0.1.11':
+ resolution: {integrity: sha512-Y2p+nGf1bF2XMttBnsVPHUWzRRZzqUoJAKmiP10b5umnO6DDrWI0BrGDJy1pOHoOULVmGSfFNkQrAlC5dcj6nQ==}
+
+ '@types/dom-webcodecs@0.1.13':
+ resolution: {integrity: sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==}
+
'@types/eslint@7.29.0':
resolution: {integrity: sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==}
@@ -8157,6 +8211,9 @@ packages:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
+ mediabunny@1.15.1:
+ resolution: {integrity: sha512-+eRTVzd3E4LuGYZzPSQcPzuGdAIljohSlzYTX358XsfLM2qH1lQIBYa+erx7wzVcGQLRNjdV7x7ZS0EpK04DfA==}
+
meilisearch@0.52.0:
resolution: {integrity: sha512-RqPsB4a78sXf/ATB7PIVvKCG7yf0y1M+uCj8Z9Wku44WmCy3iz0C1PHjVV5xphQolo09CdhdyFoRxHQSJkOdpg==}
@@ -9931,24 +9988,28 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
slacc-linux-arm64-musl@0.0.10:
resolution: {integrity: sha512-3lUX7752f6Okn54aONioaA+9M5TvifqXBAart+u2lNXEdWmmh003cVSU2Vcwg7nJ9lLHtju2DkDmKKfJjFuShA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
+ libc: [musl]
slacc-linux-x64-gnu@0.0.10:
resolution: {integrity: sha512-BxxvylF9zlOLRLCpiyMvKTIUpdLlpetNBJ+DSMDh5+Ggq+AmQz2NUGawmcBJw58F8nMCj9TpWLlGNWc2AuY+JQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
+ libc: [glibc]
slacc-linux-x64-musl@0.0.10:
resolution: {integrity: sha512-TYJi8LOtJiTFcZvka4du7bMjF9Bz1RHRwyLnScr5E5yjjgoLRrsvgSu7bxp87xH+rgJ3CdEwE3w3Ux8EiewHpA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
+ libc: [glibc]
slacc-win32-arm64-msvc@0.0.10:
resolution: {integrity: sha512-1CHPLiDB4exzFyT5ndtJDsRRhBxNg8mGz6I6eJEMjelGkJR2KZPT9LZuby/1bS/bcVOr7zuJvGNfbEGBeHRwPQ==}
@@ -10976,8 +11037,8 @@ packages:
vue-component-type-helpers@3.0.6:
resolution: {integrity: sha512-6CRM8X7EJqWCJOiKPvSLQG+hJPb/Oy2gyJx3pLjUEhY7PuaCthQu3e0zAGI1lqUBobrrk9IT0K8sG2GsCluxoQ==}
- vue-component-type-helpers@3.0.7:
- resolution: {integrity: sha512-TvyUcFXmjZcXUvU+r1MOyn4/vv4iF+tPwg5Ig33l/FJ3myZkxeQpzzQMLMFWcQAjr6Xs7BRwVy/TwbmNZUA/4w==}
+ vue-component-type-helpers@3.1.0-alpha.0:
+ resolution: {integrity: sha512-K1guwS1Oy0gNfBdIdIn8JMkUV+S38sriR1zf5dP+KkPS7/r5nHnPZUL74meY2CYlxYBH4qSQ+k7bpHfwiRvaMg==}
vue-demi@0.14.7:
resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==}
@@ -14889,7 +14950,7 @@ snapshots:
storybook: 9.1.5(@testing-library/dom@10.4.0)(bufferutil@4.0.9)(msw@2.11.1(@types/node@22.18.1)(typescript@5.9.2))(prettier@3.6.2)(utf-8-validate@6.0.5)(vite@7.1.5(@types/node@22.18.1)(sass@1.92.1)(terser@5.44.0)(tsx@4.20.5))
type-fest: 2.19.0
vue: 3.5.21(typescript@5.9.2)
- vue-component-type-helpers: 3.0.7
+ vue-component-type-helpers: 3.1.0-alpha.0
'@stylistic/eslint-plugin@2.13.0(eslint@9.35.0)(typescript@5.9.2)':
dependencies:
@@ -15241,6 +15302,12 @@ snapshots:
'@types/doctrine@0.0.9': {}
+ '@types/dom-mediacapture-transform@0.1.11':
+ dependencies:
+ '@types/dom-webcodecs': 0.1.13
+
+ '@types/dom-webcodecs@0.1.13': {}
+
'@types/eslint@7.29.0':
dependencies:
'@types/estree': 1.0.8
@@ -19688,6 +19755,11 @@ snapshots:
media-typer@0.3.0: {}
+ mediabunny@1.15.1:
+ dependencies:
+ '@types/dom-mediacapture-transform': 0.1.11
+ '@types/dom-webcodecs': 0.1.13
+
meilisearch@0.52.0: {}
memoizerific@1.11.3:
@@ -22765,7 +22837,7 @@ snapshots:
vue-component-type-helpers@3.0.6: {}
- vue-component-type-helpers@3.0.7: {}
+ vue-component-type-helpers@3.1.0-alpha.0: {}
vue-demi@0.14.7(vue@3.5.21(typescript@5.9.2)):
dependencies:
From 527b98810be7c5fcf59b0dedf2c2dc3376c32864 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Tue, 23 Sep 2025 17:29:41 +0900
Subject: [PATCH 2/6] Update CHANGELOG.md
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7ceda239ef..3672772665 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
### Client
- Feat: アカウントのQRコードを表示・読み取りできるようになりました
+- Feat: 動画を圧縮してアップロードできるようになりました
- Enhance: チャットの日本語名称がダイレクトメッセージに戻るとともに、ベータ版機能ではなくなりました
- Enhance: 画像編集にマスクエフェクト(塗りつぶし、ぼかし)を追加
- Enhance: ウォーターマークにアカウントのQRコードを追加できるように
From 214c8e86faea80cff7d7662d29b273160650ea54 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Tue, 23 Sep 2025 18:50:42 +0900
Subject: [PATCH 3/6] wip
---
locales/index.d.ts | 38 +++++++++++++++++++
locales/ja-JP.yml | 12 ++++++
.../frontend/src/composables/use-uploader.ts | 8 ++--
.../frontend/src/pages/settings/drive.vue | 35 ++++++++++++++---
packages/frontend/src/preferences/def.ts | 3 ++
5 files changed, 87 insertions(+), 9 deletions(-)
diff --git a/locales/index.d.ts b/locales/index.d.ts
index 4434848635..4071d5c373 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -5513,6 +5513,14 @@ export interface Locale extends ILocale {
* 低くすると画質を保てますが、ファイルサイズは増加します。
高くするとファイルサイズを減らせますが、画質は低下します。
*/
"defaultImageCompressionLevel_description": string;
+ /**
+ * デフォルトの圧縮度
+ */
+ "defaultCompressionLevel": string;
+ /**
+ * 低くすると品質を保てますが、ファイルサイズは増加します。
高くするとファイルサイズを減らせますが、品質は低下します。
+ */
+ "defaultCompressionLevel_description": string;
/**
* 分
*/
@@ -5545,6 +5553,36 @@ export interface Locale extends ILocale {
* ユーザー指定ノートを作成
*/
"createUserSpecifiedNote": string;
+ "_compression": {
+ "_quality": {
+ /**
+ * 高品質
+ */
+ "high": string;
+ /**
+ * 中品質
+ */
+ "medium": string;
+ /**
+ * 低品質
+ */
+ "low": string;
+ };
+ "_size": {
+ /**
+ * サイズ大
+ */
+ "large": string;
+ /**
+ * サイズ中
+ */
+ "medium": string;
+ /**
+ * サイズ小
+ */
+ "small": string;
+ };
+ };
"_order": {
/**
* 新しい順
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 0a6d020dd6..c0e598ef7b 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1373,6 +1373,8 @@ redisplayAllTips: "全ての「ヒントとコツ」を再表示"
hideAllTips: "全ての「ヒントとコツ」を非表示"
defaultImageCompressionLevel: "デフォルトの画像圧縮度"
defaultImageCompressionLevel_description: "低くすると画質を保てますが、ファイルサイズは増加します。
高くするとファイルサイズを減らせますが、画質は低下します。"
+defaultCompressionLevel: "デフォルトの圧縮度"
+defaultCompressionLevel_description: "低くすると品質を保てますが、ファイルサイズは増加します。
高くするとファイルサイズを減らせますが、品質は低下します。"
inMinutes: "分"
inDays: "日"
safeModeEnabled: "セーフモードが有効です"
@@ -1382,6 +1384,16 @@ themeIsDefaultBecauseSafeMode: "セーフモードが有効な間はデフォル
thankYouForTestingBeta: "ベータ版の検証にご協力いただきありがとうございます!"
createUserSpecifiedNote: "ユーザー指定ノートを作成"
+_compression:
+ _quality:
+ high: "高品質"
+ medium: "中品質"
+ low: "低品質"
+ _size:
+ large: "サイズ大"
+ medium: "サイズ中"
+ small: "サイズ小"
+
_order:
newest: "新しい順"
oldest: "古い順"
diff --git a/packages/frontend/src/composables/use-uploader.ts b/packages/frontend/src/composables/use-uploader.ts
index 092b0ee6d1..e291801d0e 100644
--- a/packages/frontend/src/composables/use-uploader.ts
+++ b/packages/frontend/src/composables/use-uploader.ts
@@ -145,7 +145,7 @@ export function useUploader(options: {
aborted: false,
uploaded: null,
uploadFailed: false,
- compressionLevel: prefer.s.defaultImageCompressionLevel,
+ compressionLevel: IMAGE_COMPRESSION_SUPPORTED_TYPES.includes(file.type) ? prefer.s.defaultImageCompressionLevel : VIDEO_COMPRESSION_SUPPORTED_TYPES.includes(file.type) ? prefer.s.defaultVideoCompressionLevel : 0,
watermarkPresetId: uploaderFeatures.value.watermark && $i.policies.watermarkAvailable ? prefer.s.defaultWatermarkPresetId : null,
file: markRaw(file),
});
@@ -330,7 +330,7 @@ export function useUploader(options: {
}
if (
- IMAGE_COMPRESSION_SUPPORTED_TYPES.includes(item.file.type) &&
+ (IMAGE_COMPRESSION_SUPPORTED_TYPES.includes(item.file.type) || VIDEO_COMPRESSION_SUPPORTED_TYPES.includes(item.file.type)) &&
!item.preprocessing &&
!item.uploading &&
!item.uploaded
@@ -589,7 +589,7 @@ export function useUploader(options: {
async function preprocessForVideo(item: UploaderItem): Promise {
let preprocessedFile: Blob | File = item.file;
- const needsCompress = true && VIDEO_COMPRESSION_SUPPORTED_TYPES.includes(preprocessedFile.type);
+ const needsCompress = item.compressionLevel !== 0 && VIDEO_COMPRESSION_SUPPORTED_TYPES.includes(preprocessedFile.type);
if (needsCompress) {
const mediabunny = await import('mediabunny');
@@ -611,7 +611,7 @@ export function useUploader(options: {
output,
video: {
//width: 320, // Height will be deduced automatically to retain aspect ratio
- bitrate: mediabunny.QUALITY_MEDIUM,
+ bitrate: item.compressionLevel === 1 ? mediabunny.QUALITY_VERY_HIGH : item.compressionLevel === 2 ? mediabunny.QUALITY_MEDIUM : mediabunny.QUALITY_VERY_LOW,
},
audio: {
bitrate: 32e3,
diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue
index 2d794f2e30..f58ff4c78c 100644
--- a/packages/frontend/src/pages/settings/drive.vue
+++ b/packages/frontend/src/pages/settings/drive.vue
@@ -129,13 +129,37 @@ SPDX-License-Identifier: AGPL-3.0-only
- {{ i18n.ts.defaultImageCompressionLevel }}
-
+ {{ i18n.ts.defaultCompressionLevel }}
+
+
+
+
+
+
+
+
+
+
+ {{ i18n.ts.video }}
+
+
+
+
+
+ {{ i18n.ts.defaultCompressionLevel }}
+
@@ -196,6 +220,7 @@ const meterStyle = computed(() => {
const keepOriginalFilename = prefer.model('keepOriginalFilename');
const defaultWatermarkPresetId = prefer.model('defaultWatermarkPresetId');
const defaultImageCompressionLevel = prefer.model('defaultImageCompressionLevel');
+const defaultVideoCompressionLevel = prefer.model('defaultVideoCompressionLevel');
const watermarkPresetsSyncEnabled = ref(prefer.isSyncEnabled('watermarkPresets'));
diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts
index df9c366118..a1e5ab888d 100644
--- a/packages/frontend/src/preferences/def.ts
+++ b/packages/frontend/src/preferences/def.ts
@@ -439,6 +439,9 @@ export const PREF_DEF = definePreferences({
defaultImageCompressionLevel: {
default: 2 as 0 | 1 | 2 | 3,
},
+ defaultVideoCompressionLevel: {
+ default: 2 as 0 | 1 | 2 | 3,
+ },
'sound.masterVolume': {
default: 0.5,
From 86f08300e18793503f4c1b68a2729dfda311a03f Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Tue, 23 Sep 2025 19:06:02 +0900
Subject: [PATCH 4/6] wip
---
.../frontend/src/components/MkPostForm.vue | 6 +++-
.../frontend/src/composables/use-uploader.ts | 29 +++++++++++++++++++
2 files changed, 34 insertions(+), 1 deletion(-)
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 9fec7ea4da..48b7c42654 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -105,7 +105,7 @@ SPDX-License-Identifier: AGPL-3.0-only
diff --git a/packages/frontend/src/components/MkPostFormDialog.vue b/packages/frontend/src/components/MkPostFormDialog.vue
index bf332e706e..ba8d3a7210 100644
--- a/packages/frontend/src/components/MkPostFormDialog.vue
+++ b/packages/frontend/src/components/MkPostFormDialog.vue
@@ -54,6 +54,7 @@ function onPosted() {
async function _close() {
const canClose = await form.value?.canClose();
if (!canClose) return;
+ form.value?.abortUploader();
modal.value?.close();
}
From 330f124bf6fb32df79d37e9a0548f28129d3ec87 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Tue, 23 Sep 2025 19:21:19 +0900
Subject: [PATCH 6/6] wip
---
packages/frontend/src/components/MkPostForm.vue | 2 +-
packages/frontend/src/composables/use-uploader.ts | 9 ++++++++-
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 80a64e3338..17f93a4ec8 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -219,7 +219,7 @@ const uploader = useUploader({
});
onUnmounted(() => {
- uploader.abortAll();
+ uploader.dispose();
});
uploader.events.on('itemUploaded', ctx => {
diff --git a/packages/frontend/src/composables/use-uploader.ts b/packages/frontend/src/composables/use-uploader.ts
index 581745d596..e614abf94f 100644
--- a/packages/frontend/src/composables/use-uploader.ts
+++ b/packages/frontend/src/composables/use-uploader.ts
@@ -663,10 +663,16 @@ export function useUploader(options: {
item.preprocessedFile = markRaw(preprocessedFile);
}
- onUnmounted(() => {
+ function dispose() {
for (const item of items.value) {
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
}
+
+ abortAll();
+ }
+
+ onUnmounted(() => {
+ dispose();
});
return {
@@ -674,6 +680,7 @@ export function useUploader(options: {
addFiles,
removeItem,
abortAll,
+ dispose,
upload,
getMenu,
uploading: computed(() => items.value.some(item => item.uploading)),