diff --git a/src/client/components/media-caption.vue b/src/client/components/media-caption.vue
index 690927d4c5..73eba23025 100644
--- a/src/client/components/media-caption.vue
+++ b/src/client/components/media-caption.vue
@@ -3,10 +3,13 @@
 		<div class="container">
 			<div class="fullwidth top-caption">
 				<div class="mk-dialog">
-					<header v-if="title"><Mfm :text="title"/></header>
+					<header>
+						<Mfm v-if="title" class="title" :text="title"/>
+						<span class="text-count" :class="{ over: remainingLength < 0 }">{{ remainingLength }}</span>
+					</header>
 					<textarea autofocus v-model="inputValue" :placeholder="input.placeholder" @keydown="onInputKeydown"></textarea>
 					<div class="buttons" v-if="(showOkButton || showCancelButton)">
-						<MkButton inline @click="ok" primary>{{ $ts.ok }}</MkButton>
+						<MkButton inline @click="ok" primary :disabled="remainingLength < 0">{{ $ts.ok }}</MkButton>
 						<MkButton inline @click="cancel" >{{ $ts.cancel }}</MkButton>
 					</div>
 				</div>
@@ -26,10 +29,12 @@
 
 <script lang="ts">
 import { defineComponent } from 'vue';
+import { length } from 'stringz';
 import MkModal from '@client/components/ui/modal.vue';
 import MkButton from '@client/components/ui/button.vue';
 import bytes from '@client/filters/bytes';
 import number from '@client/filters/number';
+import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits';
 
 export default defineComponent({
 	components: {
@@ -79,6 +84,13 @@ export default defineComponent({
 		document.removeEventListener('keydown', this.onKeydown);
 	},
 
+	computed: {
+		remainingLength(): number {
+			if (typeof this.inputValue != "string") return DB_MAX_IMAGE_COMMENT_LENGTH;
+			return DB_MAX_IMAGE_COMMENT_LENGTH - length(this.inputValue);
+		}
+	},
+
 	methods: {
 		bytes,
 		number,
@@ -156,8 +168,18 @@ export default defineComponent({
 
 	> header {
 		margin: 0 0 8px 0;
-		font-weight: bold;
-		font-size: 20px;
+		position: relative;
+
+		> .title {
+			font-weight: bold;
+			font-size: 20px;
+		}
+
+		> .text-count {
+			opacity: 0.7;
+			position: absolute;
+			right: 0;
+		}
 	}
 
 	> .buttons {
diff --git a/src/misc/hard-limits.ts b/src/misc/hard-limits.ts
index 2a61cb321b..1039f7335a 100644
--- a/src/misc/hard-limits.ts
+++ b/src/misc/hard-limits.ts
@@ -6,3 +6,9 @@
  * Surrogate pairs count as one
  */
 export const DB_MAX_NOTE_TEXT_LENGTH = 8192;
+
+/**
+ * Maximum image description length that can be stored in DB.
+ * Surrogate pairs count as one
+ */
+export const DB_MAX_IMAGE_COMMENT_LENGTH = 512;
diff --git a/src/misc/truncate.ts b/src/misc/truncate.ts
new file mode 100644
index 0000000000..cb120331a1
--- /dev/null
+++ b/src/misc/truncate.ts
@@ -0,0 +1,11 @@
+import { substring } from 'stringz';
+
+export function truncate(input: string, size: number): string;
+export function truncate(input: string | undefined, size: number): string | undefined;
+export function truncate(input: string | undefined, size: number): string | undefined {
+	if (!input) {
+		return input;
+	} else {
+		return substring(input, 0, size);
+	}
+}
diff --git a/src/remote/activitypub/models/image.ts b/src/remote/activitypub/models/image.ts
index cd28d59a16..89259d30fb 100644
--- a/src/remote/activitypub/models/image.ts
+++ b/src/remote/activitypub/models/image.ts
@@ -5,6 +5,8 @@ import { fetchMeta } from '@/misc/fetch-meta';
 import { apLogger } from '../logger';
 import { DriveFile } from '@/models/entities/drive-file';
 import { DriveFiles } from '@/models/index';
+import { truncate } from '@/misc/truncate';
+import { DM_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits';
 
 const logger = apLogger;
 
@@ -28,7 +30,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive
 	const instance = await fetchMeta();
 	const cache = instance.cacheRemoteFiles;
 
-	let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache, image.name);
+	let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache, truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH));
 
 	if (file.isLink) {
 		// URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、
diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts
index 4823def7cb..84b2f0c51c 100644
--- a/src/remote/activitypub/models/person.ts
+++ b/src/remote/activitypub/models/person.ts
@@ -28,22 +28,13 @@ import { getConnection } from 'typeorm';
 import { toArray } from '@/prelude/array';
 import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata';
 import { normalizeForSearch } from '@/misc/normalize-for-search';
+import { truncate } from '@/misc/truncate';
 
 const logger = apLogger;
 
 const nameLength = 128;
 const summaryLength = 2048;
 
-function truncate(input: string, size: number): string;
-function truncate(input: string | undefined, size: number): string | undefined;
-function truncate(input: string | undefined, size: number): string | undefined {
-	if (!input || input.length <= size) {
-		return input;
-	} else {
-		return input.substring(0, size);
-	}
-}
-
 /**
  * Validate and convert to actor object
  * @param x Fetched object
diff --git a/src/server/api/endpoints/drive/files/update.ts b/src/server/api/endpoints/drive/files/update.ts
index 1ef445625c..f277a9c3dc 100644
--- a/src/server/api/endpoints/drive/files/update.ts
+++ b/src/server/api/endpoints/drive/files/update.ts
@@ -4,6 +4,7 @@ import { publishDriveStream } from '@/services/stream';
 import define from '../../../define';
 import { ApiError } from '../../../error';
 import { DriveFiles, DriveFolders } from '@/models/index';
+import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits';
 
 export const meta = {
 	tags: ['drive'],
@@ -33,7 +34,7 @@ export const meta = {
 		},
 
 		comment: {
-			validator: $.optional.nullable.str,
+			validator: $.optional.nullable.str.max(DB_MAX_IMAGE_COMMENT_LENGTH),
 			default: undefined as any,
 		}
 	},
diff --git a/src/server/api/endpoints/drive/files/upload-from-url.ts b/src/server/api/endpoints/drive/files/upload-from-url.ts
index f37f316efb..9f10a42d24 100644
--- a/src/server/api/endpoints/drive/files/upload-from-url.ts
+++ b/src/server/api/endpoints/drive/files/upload-from-url.ts
@@ -5,6 +5,7 @@ import uploadFromUrl from '@/services/drive/upload-from-url';
 import define from '../../../define';
 import { DriveFiles } from '@/models/index';
 import { publishMainStream } from '@/services/stream';
+import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits';
 
 export const meta = {
 	tags: ['drive'],
@@ -35,7 +36,7 @@ export const meta = {
 		},
 
 		comment: {
-			validator: $.optional.nullable.str,
+			validator: $.optional.nullable.str.max(DB_MAX_IMAGE_COMMENT_LENGTH),
 			default: null,
 		},