@jjlmoya/utils-audiovisual 1.8.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/category/i18n/id.ts +1 -1
- package/src/category/i18n/pt.ts +1 -1
- package/src/category/i18n/ru.ts +1 -1
- package/src/category/index.ts +1 -0
- package/src/tests/category_validation.test.ts +73 -0
- package/src/tests/slug_language_code_format.test.ts +24 -0
- package/src/tests/slug_uniqueness.test.ts +81 -0
- package/src/tool/chromaticLens/i18n/ru.ts +1 -1
- package/src/tool/collageMaker/i18n/pt.ts +1 -1
- package/src/tool/imageCompressor/i18n/de.ts +1 -1
- package/src/tool/privacyBlur/i18n/nl.ts +1 -1
- package/src/tool/privacyBlur/i18n/pl.ts +1 -1
- package/src/tool/subtitleSync/i18n/ja.ts +1 -1
- package/src/tool/subtitleSync/i18n/ko.ts +1 -1
- package/src/tool/subtitleSync/i18n/zh.ts +1 -1
- package/src/tool/timelapseCalculator/i18n/ja.ts +1 -1
- package/src/tool/timelapseCalculator/i18n/ko.ts +1 -1
- package/src/tool/timelapseCalculator/i18n/zh.ts +1 -1
- package/src/tool/tvDistance/i18n/ja.ts +1 -1
- package/src/tool/tvDistance/i18n/ko.ts +1 -1
- package/src/tool/tvDistance/i18n/zh.ts +1 -1
- package/src/tool/videoFrameExtractor/i18n/ja.ts +1 -1
- package/src/tool/videoFrameExtractor/i18n/ko.ts +1 -1
- package/src/tool/videoFrameExtractor/i18n/zh.ts +1 -1
package/package.json
CHANGED
package/src/category/i18n/id.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CategoryLocaleContent } from '../../types';
|
|
2
2
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
|
-
slug: 'audiovisual-fotografi',
|
|
4
|
+
slug: 'audiovisual-dan-fotografi',
|
|
5
5
|
title: 'Alat Audiovisual dan Fotografi',
|
|
6
6
|
description: 'Alat dan kalkulator profesional untuk pembuat film, fotografer, dan pembuat konten audiovisual digital.',
|
|
7
7
|
seo: [
|
package/src/category/i18n/pt.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CategoryLocaleContent } from '../../types';
|
|
2
2
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
|
-
slug: 'audiovisual-fotografia',
|
|
4
|
+
slug: 'audiovisual-e-fotografia',
|
|
5
5
|
title: 'Ferramentas Audiovisuais e Fotografia',
|
|
6
6
|
description: 'Ferramentas profissionais e calculadoras para cineastas, fotógrafos e criadores de conteúdo audiovisual digital.',
|
|
7
7
|
seo: [
|
package/src/category/i18n/ru.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CategoryLocaleContent } from '../../types';
|
|
2
2
|
|
|
3
3
|
export const content: CategoryLocaleContent = {
|
|
4
|
-
slug: '
|
|
4
|
+
slug: 'audiovizualnye-i-foto-instrumenty',
|
|
5
5
|
title: 'Аудиовизуальные и фото инструменты',
|
|
6
6
|
description: 'Профессиональные инструменты и калькуляторы для кинематографистов, фотографов и создателей цифрового аудиовизуального контента.',
|
|
7
7
|
seo: [
|
package/src/category/index.ts
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { audiovisualCategory } from '../data';
|
|
3
|
+
import type { CategoryLocaleContent } from '../types';
|
|
4
|
+
|
|
5
|
+
const EXPECTED_LOCALES = [
|
|
6
|
+
'de', 'en', 'es', 'fr', 'id', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'ru', 'sv', 'tr', 'zh'
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
const sharingLocales = ['ja', 'ko', 'zh'];
|
|
10
|
+
|
|
11
|
+
describe('Category Validation', () => {
|
|
12
|
+
it('should have all 15 required locales', () => {
|
|
13
|
+
const registeredLocales = Object.keys(audiovisualCategory.i18n);
|
|
14
|
+
EXPECTED_LOCALES.forEach((locale) => {
|
|
15
|
+
expect(
|
|
16
|
+
registeredLocales,
|
|
17
|
+
`Category is missing locale "${locale}"`,
|
|
18
|
+
).toContain(locale);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('Category Slug Validation', () => {
|
|
23
|
+
it('every locale should have a unique, translated slug and follow format rules', async () => {
|
|
24
|
+
const slugs = new Map<string, string>();
|
|
25
|
+
const locales = Object.keys(audiovisualCategory.i18n);
|
|
26
|
+
|
|
27
|
+
let enSlug = '';
|
|
28
|
+
if (locales.includes('en')) {
|
|
29
|
+
const enLoader = audiovisualCategory.i18n['en' as keyof typeof audiovisualCategory.i18n];
|
|
30
|
+
const enContent = (await enLoader?.()) as CategoryLocaleContent;
|
|
31
|
+
enSlug = enContent.slug;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const locale of locales) {
|
|
35
|
+
const loader = audiovisualCategory.i18n[locale as keyof typeof audiovisualCategory.i18n];
|
|
36
|
+
const content = (await loader?.()) as CategoryLocaleContent;
|
|
37
|
+
|
|
38
|
+
expect(
|
|
39
|
+
content.slug,
|
|
40
|
+
`Category locale "${locale}" has an invalid slug ("${content.slug}"). Slugs must be transliterated (only a-z, 0-9, and -).`,
|
|
41
|
+
).toMatch(/^[a-z0-9-]+$/);
|
|
42
|
+
|
|
43
|
+
expect(
|
|
44
|
+
content.slug,
|
|
45
|
+
`Category locale "${locale}" slug ("${content.slug}") cannot end with a 2-letter language code (e.g., -ja, -ru, -ko).`,
|
|
46
|
+
).not.toMatch(/-[a-z]{2}$/);
|
|
47
|
+
|
|
48
|
+
if (locale !== 'en') {
|
|
49
|
+
if (sharingLocales.includes(locale)) {
|
|
50
|
+
expect(
|
|
51
|
+
content.slug,
|
|
52
|
+
`Category locale "${locale}" must use the same slug as "en" ("${enSlug}").`,
|
|
53
|
+
).toBe(enSlug);
|
|
54
|
+
} else {
|
|
55
|
+
expect(
|
|
56
|
+
content.slug,
|
|
57
|
+
`Category locale "${locale}" has the same slug as "en" ("${enSlug}"). Cada slug tiene que estar en su propio idioma`,
|
|
58
|
+
).not.toBe(enSlug);
|
|
59
|
+
|
|
60
|
+
if (slugs.has(content.slug)) {
|
|
61
|
+
const previousLocale = slugs.get(content.slug);
|
|
62
|
+
expect(
|
|
63
|
+
false,
|
|
64
|
+
`Category locales "${locale}" and "${previousLocale}" share the same slug ("${content.slug}"). Cada slug tiene que estar en su propia idioma`,
|
|
65
|
+
).toBe(true);
|
|
66
|
+
}
|
|
67
|
+
slugs.set(content.slug, locale);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ALL_TOOLS } from '../tools';
|
|
3
|
+
import type { ToolLocaleContent } from '../types';
|
|
4
|
+
|
|
5
|
+
describe('Slug Language Code Format Validation', () => {
|
|
6
|
+
ALL_TOOLS.forEach((tool) => {
|
|
7
|
+
describe(`Tool: ${tool.entry.id}`, () => {
|
|
8
|
+
it('slug should not end with 2-letter language codes like -ja, -ru, -ko', async () => {
|
|
9
|
+
const locales = Object.keys(tool.entry.i18n);
|
|
10
|
+
|
|
11
|
+
for (const locale of locales) {
|
|
12
|
+
const loader = tool.entry.i18n[locale as keyof typeof tool.entry.i18n];
|
|
13
|
+
const content = (await loader?.()) as ToolLocaleContent;
|
|
14
|
+
|
|
15
|
+
const endsWithLanguageCode = /-[a-z]{2}$/.test(content.slug) && !content.slug.endsWith('-tv') && !content.slug.endsWith('-hd');
|
|
16
|
+
expect(
|
|
17
|
+
endsWithLanguageCode,
|
|
18
|
+
`Tool "${tool.entry.id}" locale "${locale}" slug ("${content.slug}") cannot end with a 2-letter language code (e.g., -ja, -ru, -ko).`,
|
|
19
|
+
).toBe(false);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ALL_TOOLS } from '../tools';
|
|
3
|
+
import type { ToolLocaleContent } from '../types';
|
|
4
|
+
|
|
5
|
+
const sharingLocales = ['ja', 'ko', 'zh'];
|
|
6
|
+
|
|
7
|
+
interface ValidateParams {
|
|
8
|
+
toolId: string;
|
|
9
|
+
locale: string;
|
|
10
|
+
content: ToolLocaleContent;
|
|
11
|
+
enSlug: string;
|
|
12
|
+
slugs: Map<string, string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const validateLocaleSlug = ({
|
|
16
|
+
toolId,
|
|
17
|
+
locale,
|
|
18
|
+
content,
|
|
19
|
+
enSlug,
|
|
20
|
+
slugs,
|
|
21
|
+
}: ValidateParams) => {
|
|
22
|
+
expect(
|
|
23
|
+
content.slug,
|
|
24
|
+
`Tool "${toolId}" locale "${locale}" has an invalid slug ("${content.slug}"). Slugs must be transliterated (only a-z, 0-9, and -).`,
|
|
25
|
+
).toMatch(/^[a-z0-9-]+$/);
|
|
26
|
+
|
|
27
|
+
if (locale === 'en') {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (sharingLocales.includes(locale)) {
|
|
32
|
+
expect(
|
|
33
|
+
content.slug,
|
|
34
|
+
`Tool "${toolId}" locale "${locale}" must use the same slug as "en" ("${enSlug}").`,
|
|
35
|
+
).toBe(enSlug);
|
|
36
|
+
} else {
|
|
37
|
+
expect(
|
|
38
|
+
content.slug,
|
|
39
|
+
`Tool "${toolId}" locale "${locale}" has the same slug as "en" ("${enSlug}"). Cada slug tiene que estar en su propia idioma`,
|
|
40
|
+
).not.toBe(enSlug);
|
|
41
|
+
|
|
42
|
+
if (slugs.has(content.slug)) {
|
|
43
|
+
const previousLocale = slugs.get(content.slug);
|
|
44
|
+
expect(
|
|
45
|
+
false,
|
|
46
|
+
`Tool "${toolId}" locales "${locale}" and "${previousLocale}" share the same slug ("${content.slug}"). Cada slug tiene que estar en su propia idioma`,
|
|
47
|
+
).toBe(true);
|
|
48
|
+
}
|
|
49
|
+
slugs.set(content.slug, locale);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
describe('Slug Localization and Uniqueness Validation', () => {
|
|
54
|
+
ALL_TOOLS.forEach((tool) => {
|
|
55
|
+
describe(`Tool: ${tool.entry.id}`, () => {
|
|
56
|
+
it('every locale should have a unique, translated slug', async () => {
|
|
57
|
+
const slugs = new Map<string, string>();
|
|
58
|
+
const locales = Object.keys(tool.entry.i18n);
|
|
59
|
+
|
|
60
|
+
let enSlug = '';
|
|
61
|
+
if (locales.includes('en')) {
|
|
62
|
+
const enLoader = tool.entry.i18n['en' as keyof typeof tool.entry.i18n];
|
|
63
|
+
const enContent = (await enLoader?.()) as ToolLocaleContent;
|
|
64
|
+
enSlug = enContent.slug;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const locale of locales) {
|
|
68
|
+
const loader = tool.entry.i18n[locale as keyof typeof tool.entry.i18n];
|
|
69
|
+
const content = (await loader?.()) as ToolLocaleContent;
|
|
70
|
+
validateLocaleSlug({
|
|
71
|
+
toolId: tool.entry.id,
|
|
72
|
+
locale,
|
|
73
|
+
content,
|
|
74
|
+
enSlug,
|
|
75
|
+
slugs,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { ChromaticLensUI, ChromaticLensLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = '
|
|
4
|
+
const slug = 'hromatic-linza-cvetovaya-palitra-online';
|
|
5
5
|
const title = 'Chromatic Lens: онлайн извлечение цветовой палитры';
|
|
6
6
|
const description = 'Извлекайте профессиональные цветовые палитры из любого изображения бесплатно. Определяйте доминирующие цвета на ваших фотографиях с помощью математических алгоритмов.';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { CollageMakerUI, CollageMakerLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = 'criar-collage-fotos-online-
|
|
4
|
+
const slug = 'criar-collage-fotos-online-gratis-composicoes-profissionais';
|
|
5
5
|
const title = 'Criador de Collage Online: Desenhe composições profissionais';
|
|
6
6
|
const description = 'Crie colagens de fotos gratuitamente em segundos. Escolha entre vários layouts, ajuste as bordas e descarregue em alta qualidade sem marcas de água.';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { ImageCompressorUI, ImageCompressorLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = 'online-bildkomprimierer-
|
|
4
|
+
const slug = 'online-bildkomprimierer-dateigroesse-ohne-qualitaetsverlust-reduzieren';
|
|
5
5
|
const title = 'Online Bildkomprimierer: Gewicht reduzieren ohne Qualitätsverlust';
|
|
6
6
|
const description = 'Optimieren und komprimieren Sie Ihre JPG-, PNG- und WebP-Fotos kostenlos. Reduzieren Sie die Dateigröße, um die Ladegeschwindigkeit Ihrer Website lokal zu verbessern.';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { PrivacyBlurUI, PrivacyBlurLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = 'online-
|
|
4
|
+
const slug = 'online-privacyeditor-pixelate-blur-faces-fotos';
|
|
5
5
|
const title = 'Online Privacy Editor: Gezichten in foto\'s verpixelen en verbergen';
|
|
6
6
|
const description = 'Bescherm je identiteit door gevoelige delen van je foto\'s te censureren. Verpixel gezichten, vervaag documenten of dek privé-informatie 100% lokaal af.';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { PrivacyBlurUI, PrivacyBlurLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = '
|
|
4
|
+
const slug = 'edytor-prywatnosci-online-pikseluj-rozmyj-twarze-zdjecia';
|
|
5
5
|
const title = 'Edytor Prywatności Online: Pikselowanie i ukrywanie twarzy na zdjęciach';
|
|
6
6
|
const description = 'Chroń swoją tożsamość, cenzurując wrażliwe obszary swoich zdjęć. Pikseluj twarze, rozmywaj dokumenty lub zakrywaj prywatne informacje w 100% lokalnie.';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { SubtitleSyncUI, SubtitleSyncLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = 'srt-
|
|
4
|
+
const slug = 'synchronize-srt-subtitles-online-adjust-timing-free';
|
|
5
5
|
const title = 'SRT字幕オンライン同期:無料でタイミング調整';
|
|
6
6
|
const description = 'SRT字幕を早めたり遅らせたりするためのオンラインツール。時間のズレを簡単に修正し、同期されたファイルをすぐにダウンロードできます。';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { SubtitleSyncUI, SubtitleSyncLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = 'srt-
|
|
4
|
+
const slug = 'synchronize-srt-subtitles-online-adjust-timing-free';
|
|
5
5
|
const title = 'SRT 자막 온라인 동기화: 간편한 타이밍 조정';
|
|
6
6
|
const description = 'SRT 자막의 시간을 앞당기거나 늦출 수 있는 온라인 도구입니다. 자막 싱크 오차를 쉽게 수정하고 즉시 다운로드하세요.';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { SubtitleSyncUI, SubtitleSyncLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = '
|
|
4
|
+
const slug = 'synchronize-srt-subtitles-online-adjust-timing-free';
|
|
5
5
|
const title = '在线同步 SRT 字幕:免费调整时间轴';
|
|
6
6
|
const description = '在线调整 SRT 字幕提前或延迟的工具。轻松纠正时间偏差并立即下载同步后的文件。';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { TimelapseUI, TimelapseLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = 'timelapse-hyperlapse-calculator-intervals
|
|
4
|
+
const slug = 'timelapse-hyperlapse-calculator-perfect-intervals';
|
|
5
5
|
const title = 'タイムラプス・ハイパーラプス計算機:最適なインターバル設定';
|
|
6
6
|
const description = '写真撮影の間隔、動画の長さ、必要なストレージ容量を正確に計算。タイムラプス撮影に欠かせないフォトグラファー用ツール。';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { TimelapseUI, TimelapseLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = 'timelapse-hyperlapse-calculator-perfect-intervals
|
|
4
|
+
const slug = 'timelapse-hyperlapse-calculator-perfect-intervals';
|
|
5
5
|
const title = '타임랩스 및 하이퍼랩스 계산기: 완벽한 촬영 간격 설정';
|
|
6
6
|
const description = '사진 촬영 간격, 총 소요 시간, 데이터 저장 용량을 정확하게 계산하세요. 사진작가를 위한 타임랩스 필수 도구입니다.';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { TimelapseUI, TimelapseLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = 'timelapse-hyperlapse-calculator-intervals
|
|
4
|
+
const slug = 'timelapse-hyperlapse-calculator-perfect-intervals';
|
|
5
5
|
const title = '延时摄影与大范围延时摄影计算器:寻找完美拍摄间隔';
|
|
6
6
|
const description = '精确计算照片拍摄间隔、总时长以及拍摄所需的存储空间。摄影师必备的延时摄影工具。';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { TvDistanceUI, TvDistanceLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = 'tv-viewing-distance-calculator-
|
|
4
|
+
const slug = 'tv-viewing-distance-calculator-thx-4k-optimal-screen';
|
|
5
5
|
const title = 'テレビ視聴距離計算機:THX/4K最適画面設定';
|
|
6
6
|
const description = 'テレビのサイズと解像度に基づいて、理想的な視聴距離を計算。THXやSMPTE規格に準拠したホームシアター構築の最適化ツール。';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { TvDistanceUI, TvDistanceLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = 'tv-viewing-distance-calculator-
|
|
4
|
+
const slug = 'tv-viewing-distance-calculator-thx-4k-optimal-screen';
|
|
5
5
|
const title = 'TV 시청 거리 계산기: THX 및 4K 최적 화면 설정';
|
|
6
6
|
const description = 'TV 크기와 해상도에 따른 이상적인 시청 거리를 계산하세요. THX 및 SMPTE 표준을 기반으로 홈 시네마 환경을 최적화합니다.';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { TvDistanceUI, TvDistanceLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = 'tv-viewing-distance-calculator-
|
|
4
|
+
const slug = 'tv-viewing-distance-calculator-thx-4k-optimal-screen';
|
|
5
5
|
const title = '电视观看距离计算器:THX 与 4K 最佳屏幕设置';
|
|
6
6
|
const description = '根据电视尺寸和分辨率计算理想的观看距离。利用 THX 和 SMPTE 标准优化您的家庭影院体验。';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { VideoFrameExtractorUI, VideoFrameExtractorLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = 'online-video-frame-extractor-
|
|
4
|
+
const slug = 'online-video-frame-extractor-capture-hd-stills';
|
|
5
5
|
const title = 'ビデオフレーム抽出:HD静止画キャプチャ';
|
|
6
6
|
const description = 'ビデオから特定の瞬間をフレーム単位で正確に抽出。HD品質の画像をブラウザ上で安全かつ無料でキャプチャできます。';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { VideoFrameExtractorUI, VideoFrameExtractorLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = 'online-video-frame-extractor-
|
|
4
|
+
const slug = 'online-video-frame-extractor-capture-hd-stills';
|
|
5
5
|
const title = '비디오 프레임 추출기: HD 스틸 이미지 캡처';
|
|
6
6
|
const description = '비디오에서 개별 이미지를 프레임 단위의 정밀도로 추출하세요. 완벽한 순간을 HD 화질로 로컬에서 무료로 안전하게 캡처합니다.';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
2
|
import type { VideoFrameExtractorUI, VideoFrameExtractorLocaleContent } from '../index';
|
|
3
3
|
|
|
4
|
-
const slug = 'online-video-frame-extractor-
|
|
4
|
+
const slug = 'online-video-frame-extractor-capture-hd-stills';
|
|
5
5
|
const title = '视频帧提取器:高清静态图像捕获';
|
|
6
6
|
const description = '以单帧精度从视频中提取个人图像。在本地免费捕捉高清的完美瞬间。';
|
|
7
7
|
|