diff --git a/application/application.go b/application/application.go index b52b2fe8..0704b42b 100644 --- a/application/application.go +++ b/application/application.go @@ -109,6 +109,7 @@ func (s *server) Start() error { s.dep.IoIntenseQueue(context.Background()).Start() s.dep.RemoteDownloadQueue(context.Background()).Start() s.dep.MediaProcessQueue(context.Background()).Start() + s.dep.MediaVideoQueue(context.Background()).Start() // Start cron jobs c, err := crontab.NewCron(context.Background(), s.dep) diff --git a/application/dependency/dependency.go b/application/dependency/dependency.go index 163dc356..9a3b413d 100644 --- a/application/dependency/dependency.go +++ b/application/dependency/dependency.go @@ -122,6 +122,8 @@ type Dep interface { EntityRecycleQueue(ctx context.Context) queue.Queue // MediaProcessQueue Get a singleton queue.Queue instance for media post-processing (APP-101). MediaProcessQueue(ctx context.Context) queue.Queue + // MediaVideoQueue Get a singleton queue.Queue instance for deferred video transcoding (APP-103). + MediaVideoQueue(ctx context.Context) queue.Queue // MediaProcessClient Creates a new inventory.MediaProcessClient instance for the media_process_task store. MediaProcessClient() inventory.MediaProcessClient // MimeDetector Get a singleton fs.MimeDetector instance for MIME type detection. @@ -186,6 +188,7 @@ type dependency struct { mediaMetaQueue queue.Queue entityRecycleQueue queue.Queue mediaProcessQueue queue.Queue + mediaVideoQueue queue.Queue slaveQueue queue.Queue remoteDownloadQueue queue.Queue ioIntenseQueueTask queue.Task @@ -787,6 +790,40 @@ func (d *dependency) MediaProcessQueue(ctx context.Context) queue.Queue { return d.mediaProcessQueue } +func (d *dependency) MediaVideoQueue(ctx context.Context) queue.Queue { + d.mu.Lock() + defer d.mu.Unlock() + + _, reload := ctx.Value(ReloadCtx{}).(bool) + if d.mediaVideoQueue != nil && !reload { + return d.mediaVideoQueue + } + + if d.mediaVideoQueue != nil { + d.mediaVideoQueue.Shutdown() + } + + settings := d.SettingProvider() + queueSetting := settings.Queue(context.Background(), setting.QueueTypeMediaProcessVideo) + // Worker count comes from the dedicated media_compress_video_worker_num key + // (default 1) so a single long transcode stays CPU-bounded โ€” the generic queue + // worker_num getter has an upstream key bug that always yields its fallback. + workerNum := settings.MediaProcessVideo(context.Background()).WorkerNum + + d.mediaVideoQueue = queue.New(d.Logger(), d.TaskClient(), d.TaskRegistry(), d, + queue.WithBackoffFactor(queueSetting.BackoffFactor), + queue.WithMaxRetry(queueSetting.MaxRetry), + queue.WithBackoffMaxDuration(queueSetting.BackoffMaxDuration), + queue.WithRetryDelay(queueSetting.RetryDelay), + queue.WithWorkerCount(workerNum), + queue.WithName("MediaVideoQueue"), + queue.WithMaxTaskExecution(queueSetting.MaxExecution), + queue.WithResumeTaskType(queue.MediaCompressVideoTaskType), + queue.WithTaskPullInterval(10*time.Second), + ) + return d.mediaVideoQueue +} + func (d *dependency) MediaProcessClient() inventory.MediaProcessClient { if d.mediaProcessClient != nil { return d.mediaProcessClient diff --git a/assets b/assets index 5f597e0f..8788a60e 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 5f597e0fd4342a1727ff699e0235ce804bfd560b +Subproject commit 8788a60ec1edc1fc9062b2e385e778fb372b1fd3 diff --git a/inventory/mediaprocess_test.go b/inventory/mediaprocess_test.go index d8c4b8cd..c3d66613 100644 --- a/inventory/mediaprocess_test.go +++ b/inventory/mediaprocess_test.go @@ -72,6 +72,54 @@ func TestMediaProcessEnqueueIdempotent(t *testing.T) { assert.NotEqual(t, row1.ID, row3.ID, "a new pending row is created after the previous one is done") } +// TestMediaProcessEnqueueVideo covers APP-103: video rows are enqueued and listed +// independently of image rows via the media_type discriminator, sharing the same +// idempotency (active-row) and terminal-state semantics. +func TestMediaProcessEnqueueVideo(t *testing.T) { + ctx := context.Background() + client := newTestClient(t) + c := NewMediaProcessClient(client, "sqlite") + + // An image and a video row coexist on the same table. + img, err := c.Enqueue(ctx, &MediaProcessEnqueueArgs{ + EntityID: 10, FileID: 1, OwnerID: 1, MediaType: mediaprocesstask.MediaTypeImage, + }) + require.NoError(t, err) + vid, err := c.Enqueue(ctx, &MediaProcessEnqueueArgs{ + EntityID: 20, FileID: 2, OwnerID: 1, MediaType: mediaprocesstask.MediaTypeVideo, + }) + require.NoError(t, err) + assert.Equal(t, mediaprocesstask.MediaTypeVideo, vid.MediaType) + + // ListPending is scoped by the discriminator: each lane sees only its own rows. + imgs, err := c.ListPending(ctx, mediaprocesstask.MediaTypeImage, 50) + require.NoError(t, err) + assert.Len(t, imgs, 1) + assert.Equal(t, img.ID, imgs[0].ID) + + vids, err := c.ListPending(ctx, mediaprocesstask.MediaTypeVideo, 50) + require.NoError(t, err) + require.Len(t, vids, 1) + assert.Equal(t, vid.ID, vids[0].ID) + + // Idempotency guard is per entity regardless of media type. + dup, err := c.Enqueue(ctx, &MediaProcessEnqueueArgs{ + EntityID: 20, FileID: 2, OwnerID: 1, MediaType: mediaprocesstask.MediaTypeVideo, + }) + require.NoError(t, err) + assert.Equal(t, vid.ID, dup.ID, "duplicate video enqueue must reuse the active row") + + // Completing the video drops it from its lane without touching the image lane. + _, err = c.SetStatus(ctx, vid.ID, &MediaProcessStatusArgs{Status: mediaprocesstask.StatusDone, ResultSize: 42}) + require.NoError(t, err) + vids, err = c.ListPending(ctx, mediaprocesstask.MediaTypeVideo, 50) + require.NoError(t, err) + assert.Len(t, vids, 0) + imgs, err = c.ListPending(ctx, mediaprocesstask.MediaTypeImage, 50) + require.NoError(t, err) + assert.Len(t, imgs, 1, "the image lane is unaffected by video completion") +} + // TestMediaProcessHasHandledForFile covers APP-102: the backfill sweep skips // files that already have a terminal (done/skipped) row, so a re-run does not // re-compress already-processed files. diff --git a/inventory/setting.go b/inventory/setting.go index 2f432668..6b59d1d4 100644 --- a/inventory/setting.go +++ b/inventory/setting.go @@ -626,86 +626,111 @@ var DefaultSettings = map[string]string{ "queue_remote_download_max_retry": "5", "queue_remote_download_retry_delay": "0", // APP-101 โ€” media post-processing (image compression) queue tuning + cron. - "queue_media_process_max_execution": "3600", - "queue_media_process_backoff_factor": "2", - "queue_media_process_backoff_max_duration": "60", - "queue_media_process_max_retry": "3", - "queue_media_process_retry_delay": "5", - "cron_media_process": "@every 1h", + "queue_media_process_max_execution": "3600", + "queue_media_process_backoff_factor": "2", + "queue_media_process_backoff_max_duration": "60", + "queue_media_process_max_retry": "3", + "queue_media_process_retry_delay": "5", + "cron_media_process": "@every 1h", // Master switch + compression parameters. Engine defaults to vips (better for // stills; present in the deploy image alongside ffmpeg). Worker count uses a // dedicated key (the generic queue worker_num getter has an upstream key bug), // default 1 to keep CPU usage bounded. - "media_compress_image_enabled": "0", - "media_compress_engine": "vips", - "media_compress_worker_num": "1", - "media_compress_batch_size": "50", - "media_compress_image_quality": "80", - "media_compress_image_format": "keep", - "media_compress_image_args": "", - "media_compress_result_mode": "version", - "media_compress_min_size": "204800", // 200 KB - "entity_url_default_ttl": "3600", - "entity_url_cache_margin": "600", - "media_meta": "1", - "media_meta_exif": "1", - "media_meta_exif_size_local": "1073741824", - "media_meta_exif_size_remote": "104857600", - "media_meta_exif_brute_force": "1", - "media_meta_music": "1", - "media_meta_music_size_local": "1073741824", - "media_exif_music_size_remote": "1073741824", - "media_meta_ffprobe": "0", - "media_meta_ffprobe_path": "ffprobe", - "media_meta_ffprobe_size_local": "0", - "media_meta_ffprobe_size_remote": "0", - "media_meta_geocoding": "0", - "media_meta_geocoding_mapbox_ak": "", - "site_logo": "/static/img/logo.svg", - "site_logo_light": "/static/img/logo_light.svg", - "tos_url": "https://cloudreve.org/privacy-policy", - "privacy_policy_url": "https://cloudreve.org/privacy-policy", - "explorer_category_image_query": "type=file&case_folding&use_or&name=*.bmp&name=*.iff&name=*.png&name=*.gif&name=*.jpg&name=*.jpeg&name=*.psd&name=*.svg&name=*.webp&name=*.heif&name=*.heic&name=*.tiff&name=*.avif&name=*.3fr&name=*.ari&name=*.arw&name=*.bay&name=*.braw&name=*.crw&name=*.cr2&name=*.cr3&name=*.cap&name=*.dcs&name=*.dcr&name=*.dng&name=*.drf&name=*.eip&name=*.erf&name=*.fff&name=*.gpr&name=*.iiq&name=*.k25&name=*.kdc&name=*.mdc&name=*.mef&name=*.mos&name=*.mrw&name=*.nef&name=*.nrw&name=*.obm&name=*.orf&name=*.pef&name=*.ptx&name=*.pxn&name=*.r3d&name=*.raf&name=*.raw&name=*.rwl&name=*.rw2&name=*.rwz&name=*.sr2&name=*.srf&name=*.srw&name=*.tif&name=*.x3f", - "explorer_category_video_query": "type=file&case_folding&use_or&name=*.mp4&name=*.m3u8&name=*.flv&name=*.avi&name=*.wmv&name=*.mkv&name=*.rm&name=*.rmvb&name=*.mov&name=*.ogv", - "explorer_category_audio_query": "type=file&case_folding&use_or&name=*.mp3&name=*.flac&name=*.ape&name=*.wav&name=*.acc&name=*.ogg&name=*.m4a", - "explorer_category_document_query": "type=file&case_folding&use_or&name=*.pdf&name=*.doc&name=*.docx&name=*.ppt&name=*.pptx&name=*.xls&name=*.xlsx&name=*.csv&name=*.txt&name=*.md&name=*.pub", - "use_sse_for_search": "0", - "emojis": `{"๐Ÿ˜€":["๐Ÿ˜€","๐Ÿ˜ƒ","๐Ÿ˜„","๐Ÿ˜","๐Ÿ˜†","๐Ÿ˜…","๐Ÿคฃ","๐Ÿ˜‚","๐Ÿ™‚","๐Ÿ™ƒ","๐Ÿซ ","๐Ÿ˜‰","๐Ÿ˜Š","๐Ÿ˜‡","๐Ÿฅฐ","๐Ÿ˜","๐Ÿคฉ","๐Ÿ˜˜","๐Ÿ˜—","๐Ÿ˜š","๐Ÿ˜™","๐Ÿฅฒ","๐Ÿ˜‹","๐Ÿ˜›","๐Ÿ˜œ","๐Ÿคช","๐Ÿ˜","๐Ÿค‘","๐Ÿค—","๐Ÿคญ","๐Ÿซข","๐Ÿซฃ","๐Ÿคซ","๐Ÿค”","๐Ÿซก","๐Ÿค","๐Ÿคจ","๐Ÿ˜","๐Ÿ˜‘","๐Ÿ˜ถ","๐Ÿ˜ถโ€๐ŸŒซ๏ธ","๐Ÿ˜","๐Ÿ˜’","๐Ÿ™„","๐Ÿ˜ฌ","๐Ÿ˜ฎโ€๐Ÿ’จ","๐Ÿคฅ","๐Ÿ˜Œ","๐Ÿ˜”","๐Ÿ˜ช","๐Ÿคค","๐Ÿ˜ด","๐Ÿ˜ท","๐Ÿค’","๐Ÿค•","๐Ÿคข","๐Ÿคฎ","๐Ÿคง","๐Ÿฅต","๐Ÿฅถ","๐Ÿฅด","๐Ÿ˜ต","๐Ÿ˜ตโ€๐Ÿ’ซ","๐Ÿคฏ","๐Ÿค ","๐Ÿฅณ","๐Ÿฅธ","๐Ÿ˜Ž","๐Ÿค“","๐Ÿง","๐Ÿ˜•","๐Ÿซค","๐Ÿ˜Ÿ","๐Ÿ™","๐Ÿ˜ฎ","๐Ÿ˜ฏ","๐Ÿ˜ฒ","๐Ÿ˜ณ","๐Ÿฅบ","๐Ÿฅน","๐Ÿ˜ฆ","๐Ÿ˜ง","๐Ÿ˜จ","๐Ÿ˜ฐ","๐Ÿ˜ฅ","๐Ÿ˜ข","๐Ÿ˜ญ","๐Ÿ˜ฑ","๐Ÿ˜–","๐Ÿ˜ฃ","๐Ÿ˜ž","๐Ÿ˜“","๐Ÿ˜ฉ","๐Ÿ˜ซ","๐Ÿฅฑ","๐Ÿ˜ค","๐Ÿ˜ก","๐Ÿ˜ ","๐Ÿคฌ","๐Ÿ˜ˆ","๐Ÿ‘ฟ","๐Ÿ’€","โ˜ ๏ธ","๐Ÿ’ฉ","๐Ÿคก","๐Ÿ‘น","๐Ÿ‘บ","๐Ÿ‘ป","๐Ÿ‘ฝ","๐Ÿ‘พ","๐Ÿค–","๐Ÿ˜บ","๐Ÿ˜ธ","๐Ÿ˜น","๐Ÿ˜ป","๐Ÿ˜ผ","๐Ÿ˜ฝ","๐Ÿ™€","๐Ÿ˜ฟ","๐Ÿ˜พ","๐Ÿ™ˆ","๐Ÿ™‰","๐Ÿ™Š","๐Ÿ’‹","๐Ÿ’Œ","๐Ÿ’˜","๐Ÿ’","๐Ÿ’–","๐Ÿ’—","๐Ÿ’“","๐Ÿ’ž","๐Ÿ’•","๐Ÿ’Ÿ","๐Ÿ’”","โค๏ธโ€๐Ÿ”ฅ","โค๏ธโ€๐Ÿฉน","โค๏ธ","๐Ÿงก","๐Ÿ’›","๐Ÿ’š","๐Ÿ’™","๐Ÿ’œ","๐ŸคŽ","๐Ÿ–ค","๐Ÿค","๐Ÿ’ฏ","๐Ÿ’ข","๐Ÿ’ฅ","๐Ÿ’ซ","๐Ÿ’ฆ","๐Ÿ’จ","๐Ÿ•ณ๏ธ","๐Ÿ’ฃ","๐Ÿ’ฌ","๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ","๐Ÿ—จ๏ธ","๐Ÿ—ฏ๏ธ","๐Ÿ’ญ","๐Ÿ’ค"],"๐Ÿ‘‹":["๐Ÿ‘‹","๐Ÿคš","๐Ÿ–๏ธ","โœ‹","๐Ÿ––","๐Ÿซฑ","๐Ÿซฒ","๐Ÿซณ","๐Ÿซด","๐Ÿ‘Œ","๐ŸคŒ","๐Ÿค","โœŒ๏ธ","๐Ÿคž","๐Ÿซฐ","๐ŸคŸ","๐Ÿค˜","๐Ÿค™","๐Ÿ‘ˆ","๐Ÿ‘‰","๐Ÿ‘†","๐Ÿ–•","๐Ÿ‘‡","โ˜๏ธ","๐Ÿซต","๐Ÿ‘","๐Ÿ‘Ž","โœŠ","๐Ÿ‘Š","๐Ÿค›","๐Ÿคœ","๐Ÿ‘","๐Ÿ™Œ","๐Ÿซถ","๐Ÿ‘","๐Ÿคฒ","๐Ÿค","๐Ÿ™","โœ๏ธ","๐Ÿ’…","๐Ÿคณ","๐Ÿ’ช","๐Ÿฆพ","๐Ÿฆฟ","๐Ÿฆต","๐Ÿฆถ","๐Ÿ‘‚","๐Ÿฆป","๐Ÿ‘ƒ","๐Ÿง ","๐Ÿซ€","๐Ÿซ","๐Ÿฆท","๐Ÿฆด","๐Ÿ‘€","๐Ÿ‘๏ธ","๐Ÿ‘…","๐Ÿ‘„","๐Ÿซฆ","๐Ÿ‘ถ","๐Ÿง’","๐Ÿ‘ฆ","๐Ÿ‘ง","๐Ÿง‘","๐Ÿ‘ฑ","๐Ÿ‘จ","๐Ÿง”","๐Ÿง”โ€โ™‚๏ธ","๐Ÿง”โ€โ™€๏ธ","๐Ÿ‘จโ€๐Ÿฆฐ","๐Ÿ‘จโ€๐Ÿฆฑ","๐Ÿ‘จโ€๐Ÿฆณ","๐Ÿ‘จโ€๐Ÿฆฒ","๐Ÿ‘ฉ","๐Ÿ‘ฉโ€๐Ÿฆฐ","๐Ÿง‘โ€๐Ÿฆฐ","๐Ÿ‘ฉโ€๐Ÿฆฑ","๐Ÿง‘โ€๐Ÿฆฑ","๐Ÿ‘ฉโ€๐Ÿฆณ","๐Ÿง‘โ€๐Ÿฆณ","๐Ÿ‘ฉโ€๐Ÿฆฒ","๐Ÿง‘โ€๐Ÿฆฒ","๐Ÿ‘ฑโ€โ™€๏ธ","๐Ÿ‘ฑโ€โ™‚๏ธ","๐Ÿง“","๐Ÿ‘ด","๐Ÿ‘ต","๐Ÿ™","๐Ÿ™โ€โ™‚๏ธ","๐Ÿ™โ€โ™€๏ธ","๐Ÿ™Ž","๐Ÿ™Žโ€โ™‚๏ธ","๐Ÿ™Žโ€โ™€๏ธ","๐Ÿ™…","๐Ÿ™…โ€โ™‚๏ธ","๐Ÿ™…โ€โ™€๏ธ","๐Ÿ™†","๐Ÿ™†โ€โ™‚๏ธ","๐Ÿ™†โ€โ™€๏ธ","๐Ÿ’","๐Ÿ’โ€โ™‚๏ธ","๐Ÿ’โ€โ™€๏ธ","๐Ÿ™‹","๐Ÿ™‹โ€โ™‚๏ธ","๐Ÿ™‹โ€โ™€๏ธ","๐Ÿง","๐Ÿงโ€โ™‚๏ธ","๐Ÿงโ€โ™€๏ธ","๐Ÿ™‡","๐Ÿ™‡โ€โ™‚๏ธ","๐Ÿ™‡โ€โ™€๏ธ","๐Ÿคฆ","๐Ÿคฆโ€โ™‚๏ธ","๐Ÿคฆโ€โ™€๏ธ","๐Ÿคท","๐Ÿคทโ€โ™‚๏ธ","๐Ÿคทโ€โ™€๏ธ","๐Ÿง‘โ€โš•๏ธ","๐Ÿ‘จโ€โš•๏ธ","๐Ÿ‘ฉโ€โš•๏ธ","๐Ÿง‘โ€๐ŸŽ“","๐Ÿ‘จโ€๐ŸŽ“","๐Ÿ‘ฉโ€๐ŸŽ“","๐Ÿง‘โ€๐Ÿซ","๐Ÿ‘จโ€๐Ÿซ","๐Ÿ‘ฉโ€๐Ÿซ","๐Ÿง‘โ€โš–๏ธ","๐Ÿ‘จโ€โš–๏ธ","๐Ÿ‘ฉโ€โš–๏ธ","๐Ÿง‘โ€๐ŸŒพ","๐Ÿ‘จโ€๐ŸŒพ","๐Ÿ‘ฉโ€๐ŸŒพ","๐Ÿง‘โ€๐Ÿณ","๐Ÿ‘จโ€๐Ÿณ","๐Ÿ‘ฉโ€๐Ÿณ","๐Ÿง‘โ€๐Ÿ”ง","๐Ÿ‘จโ€๐Ÿ”ง","๐Ÿ‘ฉโ€๐Ÿ”ง","๐Ÿง‘โ€๐Ÿญ","๐Ÿ‘จโ€๐Ÿญ","๐Ÿ‘ฉโ€๐Ÿญ","๐Ÿง‘โ€๐Ÿ’ผ","๐Ÿ‘จโ€๐Ÿ’ผ","๐Ÿ‘ฉโ€๐Ÿ’ผ","๐Ÿง‘โ€๐Ÿ”ฌ","๐Ÿ‘จโ€๐Ÿ”ฌ","๐Ÿ‘ฉโ€๐Ÿ”ฌ","๐Ÿง‘โ€๐Ÿ’ป","๐Ÿ‘จโ€๐Ÿ’ป","๐Ÿ‘ฉโ€๐Ÿ’ป","๐Ÿง‘โ€๐ŸŽค","๐Ÿ‘จโ€๐ŸŽค","๐Ÿ‘ฉโ€๐ŸŽค","๐Ÿง‘โ€๐ŸŽจ","๐Ÿ‘จโ€๐ŸŽจ","๐Ÿ‘ฉโ€๐ŸŽจ","๐Ÿง‘โ€โœˆ๏ธ","๐Ÿ‘จโ€โœˆ๏ธ","๐Ÿ‘ฉโ€โœˆ๏ธ","๐Ÿง‘โ€๐Ÿš€","๐Ÿ‘จโ€๐Ÿš€","๐Ÿ‘ฉโ€๐Ÿš€","๐Ÿง‘โ€๐Ÿš’","๐Ÿ‘จโ€๐Ÿš’","๐Ÿ‘ฉโ€๐Ÿš’","๐Ÿ‘ฎ","๐Ÿ‘ฎโ€โ™‚๏ธ","๐Ÿ‘ฎโ€โ™€๏ธ","๐Ÿ•ต๏ธ","๐Ÿ•ต๏ธโ€โ™‚๏ธ","๐Ÿ•ต๏ธโ€โ™€๏ธ","๐Ÿ’‚","๐Ÿ’‚โ€โ™‚๏ธ","๐Ÿ’‚โ€โ™€๏ธ","๐Ÿฅท","๐Ÿ‘ท","๐Ÿ‘ทโ€โ™‚๏ธ","๐Ÿ‘ทโ€โ™€๏ธ","๐Ÿซ…","๐Ÿคด","๐Ÿ‘ธ","๐Ÿ‘ณ","๐Ÿ‘ณโ€โ™‚๏ธ","๐Ÿ‘ณโ€โ™€๏ธ","๐Ÿ‘ฒ","๐Ÿง•","๐Ÿคต","๐Ÿคตโ€โ™‚๏ธ","๐Ÿคตโ€โ™€๏ธ","๐Ÿ‘ฐ","๐Ÿ‘ฐโ€โ™‚๏ธ","๐Ÿ‘ฐโ€โ™€๏ธ","๐Ÿคฐ","๐Ÿซƒ","๐Ÿซ„","๐Ÿคฑ","๐Ÿ‘ฉโ€๐Ÿผ","๐Ÿ‘จโ€๐Ÿผ","๐Ÿง‘โ€๐Ÿผ","๐Ÿ‘ผ","๐ŸŽ…","๐Ÿคถ","๐Ÿง‘โ€๐ŸŽ„","๐Ÿฆธ","๐Ÿฆธโ€โ™‚๏ธ","๐Ÿฆธโ€โ™€๏ธ","๐Ÿฆน","๐Ÿฆนโ€โ™‚๏ธ","๐Ÿฆนโ€โ™€๏ธ","๐Ÿง™","๐Ÿง™โ€โ™‚๏ธ","๐Ÿง™โ€โ™€๏ธ","๐Ÿงš","๐Ÿงšโ€โ™‚๏ธ","๐Ÿงšโ€โ™€๏ธ","๐Ÿง›","๐Ÿง›โ€โ™‚๏ธ","๐Ÿง›โ€โ™€๏ธ","๐Ÿงœ","๐Ÿงœโ€โ™‚๏ธ","๐Ÿงœโ€โ™€๏ธ","๐Ÿง","๐Ÿงโ€โ™‚๏ธ","๐Ÿงโ€โ™€๏ธ","๐Ÿงž","๐Ÿงžโ€โ™‚๏ธ","๐Ÿงžโ€โ™€๏ธ","๐ŸงŸ","๐ŸงŸโ€โ™‚๏ธ","๐ŸงŸโ€โ™€๏ธ","๐ŸงŒ","๐Ÿ’†","๐Ÿ’†โ€โ™‚๏ธ","๐Ÿ’†โ€โ™€๏ธ","๐Ÿ’‡","๐Ÿ’‡โ€โ™‚๏ธ","๐Ÿ’‡โ€โ™€๏ธ","๐Ÿšถ","๐Ÿšถโ€โ™‚๏ธ","๐Ÿšถโ€โ™€๏ธ","๐Ÿง","๐Ÿงโ€โ™‚๏ธ","๐Ÿงโ€โ™€๏ธ","๐ŸงŽ","๐ŸงŽโ€โ™‚๏ธ","๐ŸงŽโ€โ™€๏ธ","๐Ÿง‘โ€๐Ÿฆฏ","๐Ÿ‘จโ€๐Ÿฆฏ","๐Ÿ‘ฉโ€๐Ÿฆฏ","๐Ÿง‘โ€๐Ÿฆผ","๐Ÿ‘จโ€๐Ÿฆผ","๐Ÿ‘ฉโ€๐Ÿฆผ","๐Ÿง‘โ€๐Ÿฆฝ","๐Ÿ‘จโ€๐Ÿฆฝ","๐Ÿ‘ฉโ€๐Ÿฆฝ","๐Ÿƒ","๐Ÿƒโ€โ™‚๏ธ","๐Ÿƒโ€โ™€๏ธ","๐Ÿ’ƒ","๐Ÿ•บ","๐Ÿ•ด๏ธ","๐Ÿ‘ฏ","๐Ÿ‘ฏโ€โ™‚๏ธ","๐Ÿ‘ฏโ€โ™€๏ธ","๐Ÿง–","๐Ÿง–โ€โ™‚๏ธ","๐Ÿง–โ€โ™€๏ธ","๐Ÿง—","๐Ÿง—โ€โ™‚๏ธ","๐Ÿง—โ€โ™€๏ธ","๐Ÿคบ","๐Ÿ‡","โ›ท๏ธ","๐Ÿ‚","๐ŸŒ๏ธ","๐ŸŒ๏ธโ€โ™‚๏ธ","๐ŸŒ๏ธโ€โ™€๏ธ","๐Ÿ„","๐Ÿ„โ€โ™‚๏ธ","๐Ÿ„โ€โ™€๏ธ","๐Ÿšฃ","๐Ÿšฃโ€โ™‚๏ธ","๐Ÿšฃโ€โ™€๏ธ","๐ŸŠ","๐ŸŠโ€โ™‚๏ธ","๐ŸŠโ€โ™€๏ธ","โ›น๏ธ","โ›น๏ธโ€โ™‚๏ธ","โ›น๏ธโ€โ™€๏ธ","๐Ÿ‹๏ธ","๐Ÿ‹๏ธโ€โ™‚๏ธ","๐Ÿ‹๏ธโ€โ™€๏ธ","๐Ÿšด","๐Ÿšดโ€โ™‚๏ธ","๐Ÿšดโ€โ™€๏ธ","๐Ÿšต","๐Ÿšตโ€โ™‚๏ธ","๐Ÿšตโ€โ™€๏ธ","๐Ÿคธ","๐Ÿคธโ€โ™‚๏ธ","๐Ÿคธโ€โ™€๏ธ","๐Ÿคผ","๐Ÿคผโ€โ™‚๏ธ","๐Ÿคผโ€โ™€๏ธ","๐Ÿคฝ","๐Ÿคฝโ€โ™‚๏ธ","๐Ÿคฝโ€โ™€๏ธ","๐Ÿคพ","๐Ÿคพโ€โ™‚๏ธ","๐Ÿคพโ€โ™€๏ธ","๐Ÿคน","๐Ÿคนโ€โ™‚๏ธ","๐Ÿคนโ€โ™€๏ธ","๐Ÿง˜","๐Ÿง˜โ€โ™‚๏ธ","๐Ÿง˜โ€โ™€๏ธ","๐Ÿ›€","๐Ÿ›Œ","๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘","๐Ÿ‘ญ","๐Ÿ‘ซ","๐Ÿ‘ฌ","๐Ÿ’","๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ","๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ","๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ","๐Ÿ’‘","๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘จ","๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ","๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ","๐Ÿ‘ช","๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง","๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง","๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง","๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ","๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ง","๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","๐Ÿ‘จโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘ง","๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง","๐Ÿ‘ฉโ€๐Ÿ‘ฆ","๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","๐Ÿ‘ฉโ€๐Ÿ‘ง","๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","๐Ÿ—ฃ๏ธ","๐Ÿ‘ค","๐Ÿ‘ฅ","๐Ÿซ‚","๐Ÿ‘ฃ","๐Ÿฆฐ","๐Ÿฆฑ","๐Ÿฆณ","๐Ÿฆฒ"],"๐Ÿต":["๐Ÿต","๐Ÿ’","๐Ÿฆ","๐Ÿฆง","๐Ÿถ","๐Ÿ•","๐Ÿฆฎ","๐Ÿ•โ€๐Ÿฆบ","๐Ÿฉ","๐Ÿบ","๐ŸฆŠ","๐Ÿฆ","๐Ÿฑ","๐Ÿˆ","๐Ÿˆโ€โฌ›","๐Ÿฆ","๐Ÿฏ","๐Ÿ…","๐Ÿ†","๐Ÿด","๐ŸŽ","๐Ÿฆ„","๐Ÿฆ“","๐ŸฆŒ","๐Ÿฆฌ","๐Ÿฎ","๐Ÿ‚","๐Ÿƒ","๐Ÿ„","๐Ÿท","๐Ÿ–","๐Ÿ—","๐Ÿฝ","๐Ÿ","๐Ÿ‘","๐Ÿ","๐Ÿช","๐Ÿซ","๐Ÿฆ™","๐Ÿฆ’","๐Ÿ˜","๐Ÿฆฃ","๐Ÿฆ","๐Ÿฆ›","๐Ÿญ","๐Ÿ","๐Ÿ€","๐Ÿน","๐Ÿฐ","๐Ÿ‡","๐Ÿฟ๏ธ","๐Ÿฆซ","๐Ÿฆ”","๐Ÿฆ‡","๐Ÿป","๐Ÿปโ€โ„๏ธ","๐Ÿจ","๐Ÿผ","๐Ÿฆฅ","๐Ÿฆฆ","๐Ÿฆจ","๐Ÿฆ˜","๐Ÿฆก","๐Ÿพ","๐Ÿฆƒ","๐Ÿ”","๐Ÿ“","๐Ÿฃ","๐Ÿค","๐Ÿฅ","๐Ÿฆ","๐Ÿง","๐Ÿ•Š๏ธ","๐Ÿฆ…","๐Ÿฆ†","๐Ÿฆข","๐Ÿฆ‰","๐Ÿฆค","๐Ÿชถ","๐Ÿฆฉ","๐Ÿฆš","๐Ÿฆœ","๐Ÿธ","๐ŸŠ","๐Ÿข","๐ŸฆŽ","๐Ÿ","๐Ÿฒ","๐Ÿ‰","๐Ÿฆ•","๐Ÿฆ–","๐Ÿณ","๐Ÿ‹","๐Ÿฌ","๐Ÿฆญ","๐ŸŸ","๐Ÿ ","๐Ÿก","๐Ÿฆˆ","๐Ÿ™","๐Ÿš","๐Ÿชธ","๐ŸŒ","๐Ÿฆ‹","๐Ÿ›","๐Ÿœ","๐Ÿ","๐Ÿชฒ","๐Ÿž","๐Ÿฆ—","๐Ÿชณ","๐Ÿ•ท๏ธ","๐Ÿ•ธ๏ธ","๐Ÿฆ‚","๐ŸฆŸ","๐Ÿชฐ","๐Ÿชฑ","๐Ÿฆ ","๐Ÿ’","๐ŸŒธ","๐Ÿ’ฎ","๐Ÿชท","๐Ÿต๏ธ","๐ŸŒน","๐Ÿฅ€","๐ŸŒบ","๐ŸŒป","๐ŸŒผ","๐ŸŒท","๐ŸŒฑ","๐Ÿชด","๐ŸŒฒ","๐ŸŒณ","๐ŸŒด","๐ŸŒต","๐ŸŒพ","๐ŸŒฟ","โ˜˜๏ธ","๐Ÿ€","๐Ÿ","๐Ÿ‚","๐Ÿƒ","๐Ÿชน","๐Ÿชบ"],"๐Ÿ‡":["๐Ÿ‡","๐Ÿˆ","๐Ÿ‰","๐ŸŠ","๐Ÿ‹","๐ŸŒ","๐Ÿ","๐Ÿฅญ","๐ŸŽ","๐Ÿ","๐Ÿ","๐Ÿ‘","๐Ÿ’","๐Ÿ“","๐Ÿซ","๐Ÿฅ","๐Ÿ…","๐Ÿซ’","๐Ÿฅฅ","๐Ÿฅ‘","๐Ÿ†","๐Ÿฅ”","๐Ÿฅ•","๐ŸŒฝ","๐ŸŒถ๏ธ","๐Ÿซ‘","๐Ÿฅ’","๐Ÿฅฌ","๐Ÿฅฆ","๐Ÿง„","๐Ÿง…","๐Ÿ„","๐Ÿฅœ","๐Ÿซ˜","๐ŸŒฐ","๐Ÿž","๐Ÿฅ","๐Ÿฅ–","๐Ÿซ“","๐Ÿฅจ","๐Ÿฅฏ","๐Ÿฅž","๐Ÿง‡","๐Ÿง€","๐Ÿ–","๐Ÿ—","๐Ÿฅฉ","๐Ÿฅ“","๐Ÿ”","๐ŸŸ","๐Ÿ•","๐ŸŒญ","๐Ÿฅช","๐ŸŒฎ","๐ŸŒฏ","๐Ÿซ”","๐Ÿฅ™","๐Ÿง†","๐Ÿฅš","๐Ÿณ","๐Ÿฅ˜","๐Ÿฒ","๐Ÿซ•","๐Ÿฅฃ","๐Ÿฅ—","๐Ÿฟ","๐Ÿงˆ","๐Ÿง‚","๐Ÿฅซ","๐Ÿฑ","๐Ÿ˜","๐Ÿ™","๐Ÿš","๐Ÿ›","๐Ÿœ","๐Ÿ","๐Ÿ ","๐Ÿข","๐Ÿฃ","๐Ÿค","๐Ÿฅ","๐Ÿฅฎ","๐Ÿก","๐ŸฅŸ","๐Ÿฅ ","๐Ÿฅก","๐Ÿฆ€","๐Ÿฆž","๐Ÿฆ","๐Ÿฆ‘","๐Ÿฆช","๐Ÿฆ","๐Ÿง","๐Ÿจ","๐Ÿฉ","๐Ÿช","๐ŸŽ‚","๐Ÿฐ","๐Ÿง","๐Ÿฅง","๐Ÿซ","๐Ÿฌ","๐Ÿญ","๐Ÿฎ","๐Ÿฏ","๐Ÿผ","๐Ÿฅ›","โ˜•","๐Ÿซ–","๐Ÿต","๐Ÿถ","๐Ÿพ","๐Ÿท","๐Ÿธ","๐Ÿน","๐Ÿบ","๐Ÿป","๐Ÿฅ‚","๐Ÿฅƒ","๐Ÿซ—","๐Ÿฅค","๐Ÿง‹","๐Ÿงƒ","๐Ÿง‰","๐ŸงŠ","๐Ÿฅข","๐Ÿฝ๏ธ","๐Ÿด","๐Ÿฅ„","๐Ÿ”ช","๐Ÿซ™","๐Ÿบ"],"๐ŸŒ":["๐ŸŒ","๐ŸŒŽ","๐ŸŒ","๐ŸŒ","๐Ÿ—บ๏ธ","๐Ÿ—พ","๐Ÿงญ","๐Ÿ”๏ธ","โ›ฐ๏ธ","๐ŸŒ‹","๐Ÿ—ป","๐Ÿ•๏ธ","๐Ÿ–๏ธ","๐Ÿœ๏ธ","๐Ÿ๏ธ","๐Ÿž๏ธ","๐ŸŸ๏ธ","๐Ÿ›๏ธ","๐Ÿ—๏ธ","๐Ÿงฑ","๐Ÿชจ","๐Ÿชต","๐Ÿ›–","๐Ÿ˜๏ธ","๐Ÿš๏ธ","๐Ÿ ","๐Ÿก","๐Ÿข","๐Ÿฃ","๐Ÿค","๐Ÿฅ","๐Ÿฆ","๐Ÿจ","๐Ÿฉ","๐Ÿช","๐Ÿซ","๐Ÿฌ","๐Ÿญ","๐Ÿฏ","๐Ÿฐ","๐Ÿ’’","๐Ÿ—ผ","๐Ÿ—ฝ","โ›ช","๐Ÿ•Œ","๐Ÿ›•","๐Ÿ•","โ›ฉ๏ธ","๐Ÿ•‹","โ›ฒ","โ›บ","๐ŸŒ","๐ŸŒƒ","๐Ÿ™๏ธ","๐ŸŒ„","๐ŸŒ…","๐ŸŒ†","๐ŸŒ‡","๐ŸŒ‰","โ™จ๏ธ","๐ŸŽ ","๐Ÿ›","๐ŸŽก","๐ŸŽข","๐Ÿ’ˆ","๐ŸŽช","๐Ÿš‚","๐Ÿšƒ","๐Ÿš„","๐Ÿš…","๐Ÿš†","๐Ÿš‡","๐Ÿšˆ","๐Ÿš‰","๐ŸšŠ","๐Ÿš","๐Ÿšž","๐Ÿš‹","๐ŸšŒ","๐Ÿš","๐ŸšŽ","๐Ÿš","๐Ÿš‘","๐Ÿš’","๐Ÿš“","๐Ÿš”","๐Ÿš•","๐Ÿš–","๐Ÿš—","๐Ÿš˜","๐Ÿš™","๐Ÿ›ป","๐Ÿšš","๐Ÿš›","๐Ÿšœ","๐ŸŽ๏ธ","๐Ÿ๏ธ","๐Ÿ›ต","๐Ÿฆฝ","๐Ÿฆผ","๐Ÿ›บ","๐Ÿšฒ","๐Ÿ›ด","๐Ÿ›น","๐Ÿ›ผ","๐Ÿš","๐Ÿ›ฃ๏ธ","๐Ÿ›ค๏ธ","๐Ÿ›ข๏ธ","โ›ฝ","๐Ÿ›ž","๐Ÿšจ","๐Ÿšฅ","๐Ÿšฆ","๐Ÿ›‘","๐Ÿšง","โš“","๐Ÿ›Ÿ","โ›ต","๐Ÿ›ถ","๐Ÿšค","๐Ÿ›ณ๏ธ","โ›ด๏ธ","๐Ÿ›ฅ๏ธ","๐Ÿšข","โœˆ๏ธ","๐Ÿ›ฉ๏ธ","๐Ÿ›ซ","๐Ÿ›ฌ","๐Ÿช‚","๐Ÿ’บ","๐Ÿš","๐ŸšŸ","๐Ÿš ","๐Ÿšก","๐Ÿ›ฐ๏ธ","๐Ÿš€","๐Ÿ›ธ","๐Ÿ›Ž๏ธ","๐Ÿงณ","โŒ›","โณ","โŒš","โฐ","โฑ๏ธ","โฒ๏ธ","๐Ÿ•ฐ๏ธ","๐Ÿ•›","๐Ÿ•ง","๐Ÿ•","๐Ÿ•œ","๐Ÿ•‘","๐Ÿ•","๐Ÿ•’","๐Ÿ•ž","๐Ÿ•“","๐Ÿ•Ÿ","๐Ÿ•”","๐Ÿ• ","๐Ÿ••","๐Ÿ•ก","๐Ÿ•–","๐Ÿ•ข","๐Ÿ•—","๐Ÿ•ฃ","๐Ÿ•˜","๐Ÿ•ค","๐Ÿ•™","๐Ÿ•ฅ","๐Ÿ•š","๐Ÿ•ฆ","๐ŸŒ‘","๐ŸŒ’","๐ŸŒ“","๐ŸŒ”","๐ŸŒ•","๐ŸŒ–","๐ŸŒ—","๐ŸŒ˜","๐ŸŒ™","๐ŸŒš","๐ŸŒ›","๐ŸŒœ","๐ŸŒก๏ธ","โ˜€๏ธ","๐ŸŒ","๐ŸŒž","๐Ÿช","โญ","๐ŸŒŸ","๐ŸŒ ","๐ŸŒŒ","โ˜๏ธ","โ›…","โ›ˆ๏ธ","๐ŸŒค๏ธ","๐ŸŒฅ๏ธ","๐ŸŒฆ๏ธ","๐ŸŒง๏ธ","๐ŸŒจ๏ธ","๐ŸŒฉ๏ธ","๐ŸŒช๏ธ","๐ŸŒซ๏ธ","๐ŸŒฌ๏ธ","๐ŸŒ€","๐ŸŒˆ","๐ŸŒ‚","โ˜‚๏ธ","โ˜”","โ›ฑ๏ธ","โšก","โ„๏ธ","โ˜ƒ๏ธ","โ›„","โ˜„๏ธ","๐Ÿ”ฅ","๐Ÿ’ง","๐ŸŒŠ"],"๐ŸŽƒ":["๐ŸŽƒ","๐ŸŽ„","๐ŸŽ†","๐ŸŽ‡","๐Ÿงจ","โœจ","๐ŸŽˆ","๐ŸŽ‰","๐ŸŽŠ","๐ŸŽ‹","๐ŸŽ","๐ŸŽŽ","๐ŸŽ","๐ŸŽ","๐ŸŽ‘","๐Ÿงง","๐ŸŽ€","๐ŸŽ","๐ŸŽ—๏ธ","๐ŸŽŸ๏ธ","๐ŸŽซ","๐ŸŽ–๏ธ","๐Ÿ†","๐Ÿ…","๐Ÿฅ‡","๐Ÿฅˆ","๐Ÿฅ‰","โšฝ","โšพ","๐ŸฅŽ","๐Ÿ€","๐Ÿ","๐Ÿˆ","๐Ÿ‰","๐ŸŽพ","๐Ÿฅ","๐ŸŽณ","๐Ÿ","๐Ÿ‘","๐Ÿ’","๐Ÿฅ","๐Ÿ“","๐Ÿธ","๐ŸฅŠ","๐Ÿฅ‹","๐Ÿฅ…","โ›ณ","โ›ธ๏ธ","๐ŸŽฃ","๐Ÿคฟ","๐ŸŽฝ","๐ŸŽฟ","๐Ÿ›ท","๐ŸฅŒ","๐ŸŽฏ","๐Ÿช€","๐Ÿช","๐ŸŽฑ","๐Ÿ”ฎ","๐Ÿช„","๐Ÿงฟ","๐Ÿชฌ","๐ŸŽฎ","๐Ÿ•น๏ธ","๐ŸŽฐ","๐ŸŽฒ","๐Ÿงฉ","๐Ÿงธ","๐Ÿช…","๐Ÿชฉ","๐Ÿช†","โ™ ๏ธ","โ™ฅ๏ธ","โ™ฆ๏ธ","โ™ฃ๏ธ","โ™Ÿ๏ธ","๐Ÿƒ","๐Ÿ€„","๐ŸŽด","๐ŸŽญ","๐Ÿ–ผ๏ธ","๐ŸŽจ","๐Ÿงต","๐Ÿชก","๐Ÿงถ","๐Ÿชข"],"๐Ÿ‘“":["๐Ÿ‘“","๐Ÿ•ถ๏ธ","๐Ÿฅฝ","๐Ÿฅผ","๐Ÿฆบ","๐Ÿ‘”","๐Ÿ‘•","๐Ÿ‘–","๐Ÿงฃ","๐Ÿงค","๐Ÿงฅ","๐Ÿงฆ","๐Ÿ‘—","๐Ÿ‘˜","๐Ÿฅป","๐Ÿฉฑ","๐Ÿฉฒ","๐Ÿฉณ","๐Ÿ‘™","๐Ÿ‘š","๐Ÿ‘›","๐Ÿ‘œ","๐Ÿ‘","๐Ÿ›๏ธ","๐ŸŽ’","๐Ÿฉด","๐Ÿ‘ž","๐Ÿ‘Ÿ","๐Ÿฅพ","๐Ÿฅฟ","๐Ÿ‘ ","๐Ÿ‘ก","๐Ÿฉฐ","๐Ÿ‘ข","๐Ÿ‘‘","๐Ÿ‘’","๐ŸŽฉ","๐ŸŽ“","๐Ÿงข","๐Ÿช–","โ›‘๏ธ","๐Ÿ“ฟ","๐Ÿ’„","๐Ÿ’","๐Ÿ’Ž","๐Ÿ”‡","๐Ÿ”ˆ","๐Ÿ”‰","๐Ÿ”Š","๐Ÿ“ข","๐Ÿ“ฃ","๐Ÿ“ฏ","๐Ÿ””","๐Ÿ”•","๐ŸŽผ","๐ŸŽต","๐ŸŽถ","๐ŸŽ™๏ธ","๐ŸŽš๏ธ","๐ŸŽ›๏ธ","๐ŸŽค","๐ŸŽง","๐Ÿ“ป","๐ŸŽท","๐Ÿช—","๐ŸŽธ","๐ŸŽน","๐ŸŽบ","๐ŸŽป","๐Ÿช•","๐Ÿฅ","๐Ÿช˜","๐Ÿ“ฑ","๐Ÿ“ฒ","โ˜Ž๏ธ","๐Ÿ“ž","๐Ÿ“Ÿ","๐Ÿ“ ","๐Ÿ”‹","๐Ÿชซ","๐Ÿ”Œ","๐Ÿ’ป","๐Ÿ–ฅ๏ธ","๐Ÿ–จ๏ธ","โŒจ๏ธ","๐Ÿ–ฑ๏ธ","๐Ÿ–ฒ๏ธ","๐Ÿ’ฝ","๐Ÿ’พ","๐Ÿ’ฟ","๐Ÿ“€","๐Ÿงฎ","๐ŸŽฅ","๐ŸŽž๏ธ","๐Ÿ“ฝ๏ธ","๐ŸŽฌ","๐Ÿ“บ","๐Ÿ“ท","๐Ÿ“ธ","๐Ÿ“น","๐Ÿ“ผ","๐Ÿ”","๐Ÿ”Ž","๐Ÿ•ฏ๏ธ","๐Ÿ’ก","๐Ÿ”ฆ","๐Ÿฎ","๐Ÿช”","๐Ÿ“”","๐Ÿ“•","๐Ÿ“–","๐Ÿ“—","๐Ÿ“˜","๐Ÿ“™","๐Ÿ“š","๐Ÿ““","๐Ÿ“’","๐Ÿ“ƒ","๐Ÿ“œ","๐Ÿ“„","๐Ÿ“ฐ","๐Ÿ—ž๏ธ","๐Ÿ“‘","๐Ÿ”–","๐Ÿท๏ธ","๐Ÿ’ฐ","๐Ÿช™","๐Ÿ’ด","๐Ÿ’ต","๐Ÿ’ถ","๐Ÿ’ท","๐Ÿ’ธ","๐Ÿ’ณ","๐Ÿงพ","๐Ÿ’น","โœ‰๏ธ","๐Ÿ“ง","๐Ÿ“จ","๐Ÿ“ฉ","๐Ÿ“ค","๐Ÿ“ฅ","๐Ÿ“ฆ","๐Ÿ“ซ","๐Ÿ“ช","๐Ÿ“ฌ","๐Ÿ“ญ","๐Ÿ“ฎ","๐Ÿ—ณ๏ธ","โœ๏ธ","โœ’๏ธ","๐Ÿ–‹๏ธ","๐Ÿ–Š๏ธ","๐Ÿ–Œ๏ธ","๐Ÿ–๏ธ","๐Ÿ“","๐Ÿ’ผ","๐Ÿ“","๐Ÿ“‚","๐Ÿ—‚๏ธ","๐Ÿ“…","๐Ÿ“†","๐Ÿ—’๏ธ","๐Ÿ—“๏ธ","๐Ÿ“‡","๐Ÿ“ˆ","๐Ÿ“‰","๐Ÿ“Š","๐Ÿ“‹","๐Ÿ“Œ","๐Ÿ“","๐Ÿ“Ž","๐Ÿ–‡๏ธ","๐Ÿ“","๐Ÿ“","โœ‚๏ธ","๐Ÿ—ƒ๏ธ","๐Ÿ—„๏ธ","๐Ÿ—‘๏ธ","๐Ÿ”’","๐Ÿ”“","๐Ÿ”","๐Ÿ”","๐Ÿ”‘","๐Ÿ—๏ธ","๐Ÿ”จ","๐Ÿช“","โ›๏ธ","โš’๏ธ","๐Ÿ› ๏ธ","๐Ÿ—ก๏ธ","โš”๏ธ","๐Ÿ”ซ","๐Ÿชƒ","๐Ÿน","๐Ÿ›ก๏ธ","๐Ÿชš","๐Ÿ”ง","๐Ÿช›","๐Ÿ”ฉ","โš™๏ธ","๐Ÿ—œ๏ธ","โš–๏ธ","๐Ÿฆฏ","๐Ÿ”—","โ›“๏ธ","๐Ÿช","๐Ÿงฐ","๐Ÿงฒ","๐Ÿชœ","โš—๏ธ","๐Ÿงช","๐Ÿงซ","๐Ÿงฌ","๐Ÿ”ฌ","๐Ÿ”ญ","๐Ÿ“ก","๐Ÿ’‰","๐Ÿฉธ","๐Ÿ’Š","๐Ÿฉน","๐Ÿฉผ","๐Ÿฉบ","๐Ÿฉป","๐Ÿšช","๐Ÿ›—","๐Ÿชž","๐ŸชŸ","๐Ÿ›๏ธ","๐Ÿ›‹๏ธ","๐Ÿช‘","๐Ÿšฝ","๐Ÿช ","๐Ÿšฟ","๐Ÿ›","๐Ÿชค","๐Ÿช’","๐Ÿงด","๐Ÿงท","๐Ÿงน","๐Ÿงบ","๐Ÿงป","๐Ÿชฃ","๐Ÿงผ","๐Ÿซง","๐Ÿชฅ","๐Ÿงฝ","๐Ÿงฏ","๐Ÿ›’","๐Ÿšฌ","โšฐ๏ธ","๐Ÿชฆ","โšฑ๏ธ","๐Ÿ—ฟ","๐Ÿชง","๐Ÿชช"],"๐Ÿง":["๐Ÿง","๐Ÿšฎ","๐Ÿšฐ","โ™ฟ","๐Ÿšน","๐Ÿšบ","๐Ÿšป","๐Ÿšผ","๐Ÿšพ","๐Ÿ›‚","๐Ÿ›ƒ","๐Ÿ›„","๐Ÿ›…","โš ๏ธ","๐Ÿšธ","โ›”","๐Ÿšซ","๐Ÿšณ","๐Ÿšญ","๐Ÿšฏ","๐Ÿšฑ","๐Ÿšท","๐Ÿ“ต","๐Ÿ”ž","โ˜ข๏ธ","โ˜ฃ๏ธ","โฌ†๏ธ","โ†—๏ธ","โžก๏ธ","โ†˜๏ธ","โฌ‡๏ธ","โ†™๏ธ","โฌ…๏ธ","โ†–๏ธ","โ†•๏ธ","โ†”๏ธ","โ†ฉ๏ธ","โ†ช๏ธ","โคด๏ธ","โคต๏ธ","๐Ÿ”ƒ","๐Ÿ”„","๐Ÿ”™","๐Ÿ”š","๐Ÿ”›","๐Ÿ”œ","๐Ÿ”","๐Ÿ›","โš›๏ธ","๐Ÿ•‰๏ธ","โœก๏ธ","โ˜ธ๏ธ","โ˜ฏ๏ธ","โœ๏ธ","โ˜ฆ๏ธ","โ˜ช๏ธ","โ˜ฎ๏ธ","๐Ÿ•Ž","๐Ÿ”ฏ","โ™ˆ","โ™‰","โ™Š","โ™‹","โ™Œ","โ™","โ™Ž","โ™","โ™","โ™‘","โ™’","โ™“","โ›Ž","๐Ÿ”€","๐Ÿ”","๐Ÿ”‚","โ–ถ๏ธ","โฉ","โญ๏ธ","โฏ๏ธ","โ—€๏ธ","โช","โฎ๏ธ","๐Ÿ”ผ","โซ","๐Ÿ”ฝ","โฌ","โธ๏ธ","โน๏ธ","โบ๏ธ","โ๏ธ","๐ŸŽฆ","๐Ÿ”…","๐Ÿ”†","๐Ÿ“ถ","๐Ÿ“ณ","๐Ÿ“ด","โ™€๏ธ","โ™‚๏ธ","โšง๏ธ","โœ–๏ธ","โž•","โž–","โž—","๐ŸŸฐ","โ™พ๏ธ","โ€ผ๏ธ","โ‰๏ธ","โ“","โ”","โ•","โ—","ใ€ฐ๏ธ","๐Ÿ’ฑ","๐Ÿ’ฒ","โš•๏ธ","โ™ป๏ธ","โšœ๏ธ","๐Ÿ”ฑ","๐Ÿ“›","๐Ÿ”ฐ","โญ•","โœ…","โ˜‘๏ธ","โœ”๏ธ","โŒ","โŽ","โžฐ","โžฟ","ใ€ฝ๏ธ","โœณ๏ธ","โœด๏ธ","โ‡๏ธ","ยฉ๏ธ","ยฎ๏ธ","โ„ข๏ธ","#๏ธโƒฃ","*๏ธโƒฃ","0๏ธโƒฃ","1๏ธโƒฃ","2๏ธโƒฃ","3๏ธโƒฃ","4๏ธโƒฃ","5๏ธโƒฃ","6๏ธโƒฃ","7๏ธโƒฃ","8๏ธโƒฃ","9๏ธโƒฃ","๐Ÿ”Ÿ","๐Ÿ” ","๐Ÿ”ก","๐Ÿ”ข","๐Ÿ”ฃ","๐Ÿ”ค","๐Ÿ…ฐ๏ธ","๐Ÿ†Ž","๐Ÿ…ฑ๏ธ","๐Ÿ†‘","๐Ÿ†’","๐Ÿ†“","โ„น๏ธ","๐Ÿ†”","โ“‚๏ธ","๐Ÿ†•","๐Ÿ†–","๐Ÿ…พ๏ธ","๐Ÿ†—","๐Ÿ…ฟ๏ธ","๐Ÿ†˜","๐Ÿ†™","๐Ÿ†š","๐Ÿˆ","๐Ÿˆ‚๏ธ","๐Ÿˆท๏ธ","๐Ÿˆถ","๐Ÿˆฏ","๐Ÿ‰","๐Ÿˆน","๐Ÿˆš","๐Ÿˆฒ","๐Ÿ‰‘","๐Ÿˆธ","๐Ÿˆด","๐Ÿˆณ","ใŠ—๏ธ","ใŠ™๏ธ","๐Ÿˆบ","๐Ÿˆต","๐Ÿ”ด","๐ŸŸ ","๐ŸŸก","๐ŸŸข","๐Ÿ”ต","๐ŸŸฃ","๐ŸŸค","โšซ","โšช","๐ŸŸฅ","๐ŸŸง","๐ŸŸจ","๐ŸŸฉ","๐ŸŸฆ","๐ŸŸช","๐ŸŸซ","โฌ›","โฌœ","โ—ผ๏ธ","โ—ป๏ธ","โ—พ","โ—ฝ","โ–ช๏ธ","โ–ซ๏ธ","๐Ÿ”ถ","๐Ÿ”ท","๐Ÿ”ธ","๐Ÿ”น","๐Ÿ”บ","๐Ÿ”ป","๐Ÿ’ ","๐Ÿ”˜","๐Ÿ”ณ","๐Ÿ”ฒ"],"๐Ÿ":["๐Ÿ","๐Ÿšฉ","๐ŸŽŒ","๐Ÿด","๐Ÿณ๏ธ","๐Ÿณ๏ธโ€๐ŸŒˆ","๐Ÿณ๏ธโ€โšง๏ธ","๐Ÿดโ€โ˜ ๏ธ","๐Ÿ‡ฆ๐Ÿ‡จ","๐Ÿ‡ฆ๐Ÿ‡ฉ","๐Ÿ‡ฆ๐Ÿ‡ช","๐Ÿ‡ฆ๐Ÿ‡ซ","๐Ÿ‡ฆ๐Ÿ‡ฌ","๐Ÿ‡ฆ๐Ÿ‡ฎ","๐Ÿ‡ฆ๐Ÿ‡ฑ","๐Ÿ‡ฆ๐Ÿ‡ฒ","๐Ÿ‡ฆ๐Ÿ‡ด","๐Ÿ‡ฆ๐Ÿ‡ถ","๐Ÿ‡ฆ๐Ÿ‡ท","๐Ÿ‡ฆ๐Ÿ‡ธ","๐Ÿ‡ฆ๐Ÿ‡น","๐Ÿ‡ฆ๐Ÿ‡บ","๐Ÿ‡ฆ๐Ÿ‡ผ","๐Ÿ‡ฆ๐Ÿ‡ฝ","๐Ÿ‡ฆ๐Ÿ‡ฟ","๐Ÿ‡ง๐Ÿ‡ฆ","๐Ÿ‡ง๐Ÿ‡ง","๐Ÿ‡ง๐Ÿ‡ฉ","๐Ÿ‡ง๐Ÿ‡ช","๐Ÿ‡ง๐Ÿ‡ซ","๐Ÿ‡ง๐Ÿ‡ฌ","๐Ÿ‡ง๐Ÿ‡ญ","๐Ÿ‡ง๐Ÿ‡ฎ","๐Ÿ‡ง๐Ÿ‡ฏ","๐Ÿ‡ง๐Ÿ‡ฑ","๐Ÿ‡ง๐Ÿ‡ฒ","๐Ÿ‡ง๐Ÿ‡ณ","๐Ÿ‡ง๐Ÿ‡ด","๐Ÿ‡ง๐Ÿ‡ถ","๐Ÿ‡ง๐Ÿ‡ท","๐Ÿ‡ง๐Ÿ‡ธ","๐Ÿ‡ง๐Ÿ‡น","๐Ÿ‡ง๐Ÿ‡ป","๐Ÿ‡ง๐Ÿ‡ผ","๐Ÿ‡ง๐Ÿ‡พ","๐Ÿ‡ง๐Ÿ‡ฟ","๐Ÿ‡จ๐Ÿ‡ฆ","๐Ÿ‡จ๐Ÿ‡จ","๐Ÿ‡จ๐Ÿ‡ฉ","๐Ÿ‡จ๐Ÿ‡ซ","๐Ÿ‡จ๐Ÿ‡ฌ","๐Ÿ‡จ๐Ÿ‡ญ","๐Ÿ‡จ๐Ÿ‡ฎ","๐Ÿ‡จ๐Ÿ‡ฐ","๐Ÿ‡จ๐Ÿ‡ฑ","๐Ÿ‡จ๐Ÿ‡ฒ","๐Ÿ‡จ๐Ÿ‡ณ","๐Ÿ‡จ๐Ÿ‡ด","๐Ÿ‡จ๐Ÿ‡ต","๐Ÿ‡จ๐Ÿ‡ท","๐Ÿ‡จ๐Ÿ‡บ","๐Ÿ‡จ๐Ÿ‡ป","๐Ÿ‡จ๐Ÿ‡ผ","๐Ÿ‡จ๐Ÿ‡ฝ","๐Ÿ‡จ๐Ÿ‡พ","๐Ÿ‡จ๐Ÿ‡ฟ","๐Ÿ‡ฉ๐Ÿ‡ช","๐Ÿ‡ฉ๐Ÿ‡ฌ","๐Ÿ‡ฉ๐Ÿ‡ฏ","๐Ÿ‡ฉ๐Ÿ‡ฐ","๐Ÿ‡ฉ๐Ÿ‡ฒ","๐Ÿ‡ฉ๐Ÿ‡ด","๐Ÿ‡ฉ๐Ÿ‡ฟ","๐Ÿ‡ช๐Ÿ‡ฆ","๐Ÿ‡ช๐Ÿ‡จ","๐Ÿ‡ช๐Ÿ‡ช","๐Ÿ‡ช๐Ÿ‡ฌ","๐Ÿ‡ช๐Ÿ‡ญ","๐Ÿ‡ช๐Ÿ‡ท","๐Ÿ‡ช๐Ÿ‡ธ","๐Ÿ‡ช๐Ÿ‡น","๐Ÿ‡ช๐Ÿ‡บ","๐Ÿ‡ซ๐Ÿ‡ฎ","๐Ÿ‡ซ๐Ÿ‡ฏ","๐Ÿ‡ซ๐Ÿ‡ฐ","๐Ÿ‡ซ๐Ÿ‡ฒ","๐Ÿ‡ซ๐Ÿ‡ด","๐Ÿ‡ซ๐Ÿ‡ท","๐Ÿ‡ฌ๐Ÿ‡ฆ","๐Ÿ‡ฌ๐Ÿ‡ง","๐Ÿ‡ฌ๐Ÿ‡ฉ","๐Ÿ‡ฌ๐Ÿ‡ช","๐Ÿ‡ฌ๐Ÿ‡ซ","๐Ÿ‡ฌ๐Ÿ‡ฌ","๐Ÿ‡ฌ๐Ÿ‡ญ","๐Ÿ‡ฌ๐Ÿ‡ฎ","๐Ÿ‡ฌ๐Ÿ‡ฑ","๐Ÿ‡ฌ๐Ÿ‡ฒ","๐Ÿ‡ฌ๐Ÿ‡ณ","๐Ÿ‡ฌ๐Ÿ‡ต","๐Ÿ‡ฌ๐Ÿ‡ถ","๐Ÿ‡ฌ๐Ÿ‡ท","๐Ÿ‡ฌ๐Ÿ‡ธ","๐Ÿ‡ฌ๐Ÿ‡น","๐Ÿ‡ฌ๐Ÿ‡บ","๐Ÿ‡ฌ๐Ÿ‡ผ","๐Ÿ‡ฌ๐Ÿ‡พ","๐Ÿ‡ญ๐Ÿ‡ฐ","๐Ÿ‡ญ๐Ÿ‡ฒ","๐Ÿ‡ญ๐Ÿ‡ณ","๐Ÿ‡ญ๐Ÿ‡ท","๐Ÿ‡ญ๐Ÿ‡น","๐Ÿ‡ญ๐Ÿ‡บ","๐Ÿ‡ฎ๐Ÿ‡จ","๐Ÿ‡ฎ๐Ÿ‡ฉ","๐Ÿ‡ฎ๐Ÿ‡ช","๐Ÿ‡ฎ๐Ÿ‡ฑ","๐Ÿ‡ฎ๐Ÿ‡ฒ","๐Ÿ‡ฎ๐Ÿ‡ณ","๐Ÿ‡ฎ๐Ÿ‡ด","๐Ÿ‡ฎ๐Ÿ‡ถ","๐Ÿ‡ฎ๐Ÿ‡ท","๐Ÿ‡ฎ๐Ÿ‡ธ","๐Ÿ‡ฎ๐Ÿ‡น","๐Ÿ‡ฏ๐Ÿ‡ช","๐Ÿ‡ฏ๐Ÿ‡ฒ","๐Ÿ‡ฏ๐Ÿ‡ด","๐Ÿ‡ฏ๐Ÿ‡ต","๐Ÿ‡ฐ๐Ÿ‡ช","๐Ÿ‡ฐ๐Ÿ‡ฌ","๐Ÿ‡ฐ๐Ÿ‡ญ","๐Ÿ‡ฐ๐Ÿ‡ฎ","๐Ÿ‡ฐ๐Ÿ‡ฒ","๐Ÿ‡ฐ๐Ÿ‡ณ","๐Ÿ‡ฐ๐Ÿ‡ต","๐Ÿ‡ฐ๐Ÿ‡ท","๐Ÿ‡ฐ๐Ÿ‡ผ","๐Ÿ‡ฐ๐Ÿ‡พ","๐Ÿ‡ฐ๐Ÿ‡ฟ","๐Ÿ‡ฑ๐Ÿ‡ฆ","๐Ÿ‡ฑ๐Ÿ‡ง","๐Ÿ‡ฑ๐Ÿ‡จ","๐Ÿ‡ฑ๐Ÿ‡ฎ","๐Ÿ‡ฑ๐Ÿ‡ฐ","๐Ÿ‡ฑ๐Ÿ‡ท","๐Ÿ‡ฑ๐Ÿ‡ธ","๐Ÿ‡ฑ๐Ÿ‡น","๐Ÿ‡ฑ๐Ÿ‡บ","๐Ÿ‡ฑ๐Ÿ‡ป","๐Ÿ‡ฑ๐Ÿ‡พ","๐Ÿ‡ฒ๐Ÿ‡ฆ","๐Ÿ‡ฒ๐Ÿ‡จ","๐Ÿ‡ฒ๐Ÿ‡ฉ","๐Ÿ‡ฒ๐Ÿ‡ช","๐Ÿ‡ฒ๐Ÿ‡ซ","๐Ÿ‡ฒ๐Ÿ‡ฌ","๐Ÿ‡ฒ๐Ÿ‡ญ","๐Ÿ‡ฒ๐Ÿ‡ฐ","๐Ÿ‡ฒ๐Ÿ‡ฑ","๐Ÿ‡ฒ๐Ÿ‡ฒ","๐Ÿ‡ฒ๐Ÿ‡ณ","๐Ÿ‡ฒ๐Ÿ‡ด","๐Ÿ‡ฒ๐Ÿ‡ต","๐Ÿ‡ฒ๐Ÿ‡ถ","๐Ÿ‡ฒ๐Ÿ‡ท","๐Ÿ‡ฒ๐Ÿ‡ธ","๐Ÿ‡ฒ๐Ÿ‡น","๐Ÿ‡ฒ๐Ÿ‡บ","๐Ÿ‡ฒ๐Ÿ‡ป","๐Ÿ‡ฒ๐Ÿ‡ผ","๐Ÿ‡ฒ๐Ÿ‡ฝ","๐Ÿ‡ฒ๐Ÿ‡พ","๐Ÿ‡ฒ๐Ÿ‡ฟ","๐Ÿ‡ณ๐Ÿ‡ฆ","๐Ÿ‡ณ๐Ÿ‡จ","๐Ÿ‡ณ๐Ÿ‡ช","๐Ÿ‡ณ๐Ÿ‡ซ","๐Ÿ‡ณ๐Ÿ‡ฌ","๐Ÿ‡ณ๐Ÿ‡ฎ","๐Ÿ‡ณ๐Ÿ‡ฑ","๐Ÿ‡ณ๐Ÿ‡ด","๐Ÿ‡ณ๐Ÿ‡ต","๐Ÿ‡ณ๐Ÿ‡ท","๐Ÿ‡ณ๐Ÿ‡บ","๐Ÿ‡ณ๐Ÿ‡ฟ","๐Ÿ‡ด๐Ÿ‡ฒ","๐Ÿ‡ต๐Ÿ‡ฆ","๐Ÿ‡ต๐Ÿ‡ช","๐Ÿ‡ต๐Ÿ‡ซ","๐Ÿ‡ต๐Ÿ‡ฌ","๐Ÿ‡ต๐Ÿ‡ญ","๐Ÿ‡ต๐Ÿ‡ฐ","๐Ÿ‡ต๐Ÿ‡ฑ","๐Ÿ‡ต๐Ÿ‡ฒ","๐Ÿ‡ต๐Ÿ‡ณ","๐Ÿ‡ต๐Ÿ‡ท","๐Ÿ‡ต๐Ÿ‡ธ","๐Ÿ‡ต๐Ÿ‡น","๐Ÿ‡ต๐Ÿ‡ผ","๐Ÿ‡ต๐Ÿ‡พ","๐Ÿ‡ถ๐Ÿ‡ฆ","๐Ÿ‡ท๐Ÿ‡ช","๐Ÿ‡ท๐Ÿ‡ด","๐Ÿ‡ท๐Ÿ‡ธ","๐Ÿ‡ท๐Ÿ‡บ","๐Ÿ‡ท๐Ÿ‡ผ","๐Ÿ‡ธ๐Ÿ‡ฆ","๐Ÿ‡ธ๐Ÿ‡ง","๐Ÿ‡ธ๐Ÿ‡จ","๐Ÿ‡ธ๐Ÿ‡ฉ","๐Ÿ‡ธ๐Ÿ‡ช","๐Ÿ‡ธ๐Ÿ‡ฌ","๐Ÿ‡ธ๐Ÿ‡ญ","๐Ÿ‡ธ๐Ÿ‡ฎ","๐Ÿ‡ธ๐Ÿ‡ฏ","๐Ÿ‡ธ๐Ÿ‡ฐ","๐Ÿ‡ธ๐Ÿ‡ฑ","๐Ÿ‡ธ๐Ÿ‡ฒ","๐Ÿ‡ธ๐Ÿ‡ณ","๐Ÿ‡ธ๐Ÿ‡ด","๐Ÿ‡ธ๐Ÿ‡ท","๐Ÿ‡ธ๐Ÿ‡ธ","๐Ÿ‡ธ๐Ÿ‡น","๐Ÿ‡ธ๐Ÿ‡ป","๐Ÿ‡ธ๐Ÿ‡ฝ","๐Ÿ‡ธ๐Ÿ‡พ","๐Ÿ‡ธ๐Ÿ‡ฟ","๐Ÿ‡น๐Ÿ‡ฆ","๐Ÿ‡น๐Ÿ‡จ","๐Ÿ‡น๐Ÿ‡ฉ","๐Ÿ‡น๐Ÿ‡ซ","๐Ÿ‡น๐Ÿ‡ฌ","๐Ÿ‡น๐Ÿ‡ญ","๐Ÿ‡น๐Ÿ‡ฏ","๐Ÿ‡น๐Ÿ‡ฐ","๐Ÿ‡น๐Ÿ‡ฑ","๐Ÿ‡น๐Ÿ‡ฒ","๐Ÿ‡น๐Ÿ‡ณ","๐Ÿ‡น๐Ÿ‡ด","๐Ÿ‡น๐Ÿ‡ท","๐Ÿ‡น๐Ÿ‡น","๐Ÿ‡น๐Ÿ‡ป","๐Ÿ‡น๐Ÿ‡ผ","๐Ÿ‡น๐Ÿ‡ฟ","๐Ÿ‡บ๐Ÿ‡ฆ","๐Ÿ‡บ๐Ÿ‡ฌ","๐Ÿ‡บ๐Ÿ‡ฒ","๐Ÿ‡บ๐Ÿ‡ณ","๐Ÿ‡บ๐Ÿ‡ธ","๐Ÿ‡บ๐Ÿ‡พ","๐Ÿ‡บ๐Ÿ‡ฟ","๐Ÿ‡ป๐Ÿ‡ฆ","๐Ÿ‡ป๐Ÿ‡จ","๐Ÿ‡ป๐Ÿ‡ช","๐Ÿ‡ป๐Ÿ‡ฌ","๐Ÿ‡ป๐Ÿ‡ฎ","๐Ÿ‡ป๐Ÿ‡ณ","๐Ÿ‡ป๐Ÿ‡บ","๐Ÿ‡ผ๐Ÿ‡ซ","๐Ÿ‡ผ๐Ÿ‡ธ","๐Ÿ‡ฝ๐Ÿ‡ฐ","๐Ÿ‡พ๐Ÿ‡ช","๐Ÿ‡พ๐Ÿ‡น","๐Ÿ‡ฟ๐Ÿ‡ฆ","๐Ÿ‡ฟ๐Ÿ‡ฒ","๐Ÿ‡ฟ๐Ÿ‡ผ","๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ","๐Ÿด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ","๐Ÿด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ"]}`, - "map_provider": "openstreetmap", - "map_google_tile_type": "regular", - "map_mapbox_ak": "", - "mime_mapping": `{".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",".xltx":"application/vnd.openxmlformats-officedocument.spreadsheetml.template",".potx":"application/vnd.openxmlformats-officedocument.presentationml.template",".ppsx":"application/vnd.openxmlformats-officedocument.presentationml.slideshow",".pptx":"application/vnd.openxmlformats-officedocument.presentationml.presentation",".sldx":"application/vnd.openxmlformats-officedocument.presentationml.slide",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".dotx":"application/vnd.openxmlformats-officedocument.wordprocessingml.template",".xlam":"application/vnd.ms-excel.addin.macroEnabled.12",".xlsb":"application/vnd.ms-excel.sheet.binary.macroEnabled.12",".apk":"application/vnd.android.package-archive",".hqx":"application/mac-binhex40",".cpt":"application/mac-compactpro",".doc":"application/msword",".ogg":"application/ogg",".pdf":"application/pdf",".rtf":"text/rtf",".mif":"application/vnd.mif",".xls":"application/vnd.ms-excel",".ppt":"application/vnd.ms-powerpoint",".odc":"application/vnd.oasis.opendocument.chart",".odb":"application/vnd.oasis.opendocument.database",".odf":"application/vnd.oasis.opendocument.formula",".odg":"application/vnd.oasis.opendocument.graphics",".otg":"application/vnd.oasis.opendocument.graphics-template",".odi":"application/vnd.oasis.opendocument.image",".odp":"application/vnd.oasis.opendocument.presentation",".otp":"application/vnd.oasis.opendocument.presentation-template",".ods":"application/vnd.oasis.opendocument.spreadsheet",".ots":"application/vnd.oasis.opendocument.spreadsheet-template",".odt":"application/vnd.oasis.opendocument.text",".odm":"application/vnd.oasis.opendocument.text-master",".ott":"application/vnd.oasis.opendocument.text-template",".oth":"application/vnd.oasis.opendocument.text-web",".sxw":"application/vnd.sun.xml.writer",".stw":"application/vnd.sun.xml.writer.template",".sxc":"application/vnd.sun.xml.calc",".stc":"application/vnd.sun.xml.calc.template",".sxd":"application/vnd.sun.xml.draw",".std":"application/vnd.sun.xml.draw.template",".sxi":"application/vnd.sun.xml.impress",".sti":"application/vnd.sun.xml.impress.template",".sxg":"application/vnd.sun.xml.writer.global",".sxm":"application/vnd.sun.xml.math",".sis":"application/vnd.symbian.install",".wbxml":"application/vnd.wap.wbxml",".wmlc":"application/vnd.wap.wmlc",".wmlsc":"application/vnd.wap.wmlscriptc",".bcpio":"application/x-bcpio",".torrent":"application/x-bittorrent",".bz2":"application/x-bzip2",".vcd":"application/x-cdlink",".pgn":"application/x-chess-pgn",".cpio":"application/x-cpio",".csh":"application/x-csh",".dvi":"application/x-dvi",".spl":"application/x-futuresplash",".gtar":"application/x-gtar",".hdf":"application/x-hdf",".jar":"application/x-java-archive",".jnlp":"application/x-java-jnlp-file",".js":"application/x-javascript",".ksp":"application/x-kspread",".chrt":"application/x-kchart",".kil":"application/x-killustrator",".latex":"application/x-latex",".rpm":"application/x-rpm",".sh":"application/x-sh",".shar":"application/x-shar",".swf":"application/x-shockwave-flash",".sit":"application/x-stuffit",".sv4cpio":"application/x-sv4cpio",".sv4crc":"application/x-sv4crc",".tar":"application/x-tar",".tcl":"application/x-tcl",".tex":"application/x-tex",".man":"application/x-troff-man",".me":"application/x-troff-me",".ms":"application/x-troff-ms",".ustar":"application/x-ustar",".src":"application/x-wais-source",".zip":"application/zip",".m3u":"audio/x-mpegurl",".ra":"audio/x-pn-realaudio",".wav":"audio/x-wav",".wma":"audio/x-ms-wma",".wax":"audio/x-ms-wax",".pdb":"chemical/x-pdb",".xyz":"chemical/x-xyz",".bmp":"image/bmp",".gif":"image/gif",".ief":"image/ief",".png":"image/png",".wbmp":"image/vnd.wap.wbmp",".ras":"image/x-cmu-raster",".pnm":"image/x-portable-anymap",".pbm":"image/x-portable-bitmap",".pgm":"image/x-portable-graymap",".ppm":"image/x-portable-pixmap",".rgb":"image/x-rgb",".xbm":"image/x-xbitmap",".xpm":"image/x-xpixmap",".xwd":"image/x-xwindowdump",".css":"text/css",".rtx":"text/richtext",".tsv":"text/tab-separated-values",".jad":"text/vnd.sun.j2me.app-descriptor",".wml":"text/vnd.wap.wml",".wmls":"text/vnd.wap.wmlscript",".etx":"text/x-setext",".mxu":"video/vnd.mpegurl",".flv":"video/x-flv",".wm":"video/x-ms-wm",".wmv":"video/x-ms-wmv",".wmx":"video/x-ms-wmx",".wvx":"video/x-ms-wvx",".avi":"video/x-msvideo",".movie":"video/x-sgi-movie",".ice":"x-conference/x-cooltalk",".3gp":"video/3gpp",".ai":"application/postscript",".aif":"audio/x-aiff",".aifc":"audio/x-aiff",".aiff":"audio/x-aiff",".asc":"text/plain",".atom":"application/atom+xml",".au":"audio/basic",".bin":"application/octet-stream",".cdf":"application/x-netcdf",".cgm":"image/cgm",".class":"application/octet-stream",".dcr":"application/x-director",".dif":"video/x-dv",".dir":"application/x-director",".djv":"image/vnd.djvu",".djvu":"image/vnd.djvu",".dll":"application/octet-stream",".dmg":"application/octet-stream",".dms":"application/octet-stream",".dtd":"application/xml-dtd",".dv":"video/x-dv",".dxr":"application/x-director",".eps":"application/postscript",".exe":"application/octet-stream",".ez":"application/andrew-inset",".gram":"application/srgs",".grxml":"application/srgs+xml",".gz":"application/x-gzip",".htm":"text/html",".html":"text/html",".ico":"image/x-icon",".ics":"text/calendar",".ifb":"text/calendar",".iges":"model/iges",".igs":"model/iges",".jp2":"image/jp2",".jpe":"image/jpeg",".jpeg":"image/jpeg",".jpg":"image/jpeg",".kar":"audio/midi",".lha":"application/octet-stream",".lzh":"application/octet-stream",".m4a":"audio/mp4a-latm",".m4p":"audio/mp4a-latm",".m4u":"video/vnd.mpegurl",".m4v":"video/x-m4v",".mac":"image/x-macpaint",".mathml":"application/mathml+xml",".mesh":"model/mesh",".mid":"audio/midi",".midi":"audio/midi",".mov":"video/quicktime",".mp2":"audio/mpeg",".mp3":"audio/mpeg",".mp4":"video/mp4",".mpe":"video/mpeg",".mpeg":"video/mpeg",".mpg":"video/mpeg",".mpga":"audio/mpeg",".msh":"model/mesh",".nc":"application/x-netcdf",".oda":"application/oda",".ogv":"video/ogv",".pct":"image/pict",".pic":"image/pict",".pict":"image/pict",".pnt":"image/x-macpaint",".pntg":"image/x-macpaint",".ps":"application/postscript",".qt":"video/quicktime",".qti":"image/x-quicktime",".qtif":"image/x-quicktime",".ram":"audio/x-pn-realaudio",".rdf":"application/rdf+xml",".rm":"application/vnd.rn-realmedia",".roff":"application/x-troff",".sgm":"text/sgml",".sgml":"text/sgml",".silo":"model/mesh",".skd":"application/x-koan",".skm":"application/x-koan",".skp":"application/x-koan",".skt":"application/x-koan",".smi":"application/smil",".smil":"application/smil",".snd":"audio/basic",".so":"application/octet-stream",".svg":"image/svg+xml",".t":"application/x-troff",".texi":"application/x-texinfo",".texinfo":"application/x-texinfo",".tif":"image/tiff",".tiff":"image/tiff",".tr":"application/x-troff",".txt":"text/plain; charset=utf-8",".vrml":"model/vrml",".vxml":"application/voicexml+xml",".webm":"video/webm",".wrl":"model/vrml",".xht":"application/xhtml+xml",".xhtml":"application/xhtml+xml",".xml":"application/xml",".xsl":"application/xml",".xslt":"application/xslt+xml",".xul":"application/vnd.mozilla.xul+xml",".webp":"image/webp",".323":"text/h323",".aab":"application/x-authoware-bin",".aam":"application/x-authoware-map",".aas":"application/x-authoware-seg",".acx":"application/internet-property-stream",".als":"audio/X-Alpha5",".amc":"application/x-mpeg",".ani":"application/octet-stream",".asd":"application/astound",".asf":"video/x-ms-asf",".asn":"application/astound",".asp":"application/x-asap",".asr":"video/x-ms-asf",".asx":"video/x-ms-asf",".avb":"application/octet-stream",".awb":"audio/amr-wb",".axs":"application/olescript",".bas":"text/plain",".bin ":"application/octet-stream",".bld":"application/bld",".bld2":"application/bld2",".bpk":"application/octet-stream",".c":"text/plain",".cal":"image/x-cals",".cat":"application/vnd.ms-pkiseccat",".ccn":"application/x-cnc",".cco":"application/x-cocoa",".cer":"application/x-x509-ca-cert",".cgi":"magnus-internal/cgi",".chat":"application/x-chat",".clp":"application/x-msclip",".cmx":"image/x-cmx",".co":"application/x-cult3d-object",".cod":"image/cis-cod",".conf":"text/plain",".cpp":"text/plain",".crd":"application/x-mscardfile",".crl":"application/pkix-crl",".crt":"application/x-x509-ca-cert",".csm":"chemical/x-csml",".csml":"chemical/x-csml",".cur":"application/octet-stream",".dcm":"x-lml/x-evm",".dcx":"image/x-dcx",".der":"application/x-x509-ca-cert",".dhtml":"text/html",".dot":"application/msword",".dwf":"drawing/x-dwf",".dwg":"application/x-autocad",".dxf":"application/x-autocad",".ebk":"application/x-expandedbook",".emb":"chemical/x-embl-dl-nucleotide",".embl":"chemical/x-embl-dl-nucleotide",".epub":"application/epub+zip",".eri":"image/x-eri",".es":"audio/echospeech",".esl":"audio/echospeech",".etc":"application/x-earthtime",".evm":"x-lml/x-evm",".evy":"application/envoy",".fh4":"image/x-freehand",".fh5":"image/x-freehand",".fhc":"image/x-freehand",".fif":"application/fractals",".flr":"x-world/x-vrml",".fm":"application/x-maker",".fpx":"image/x-fpx",".fvi":"video/isivideo",".gau":"chemical/x-gaussian-input",".gca":"application/x-gca-compressed",".gdb":"x-lml/x-gdb",".gps":"application/x-gps",".h":"text/plain",".hdm":"text/x-hdml",".hdml":"text/x-hdml",".hlp":"application/winhlp",".hta":"application/hta",".htc":"text/x-component",".hts":"text/html",".htt":"text/webviewhtml",".ifm":"image/gif",".ifs":"image/ifs",".iii":"application/x-iphone",".imy":"audio/melody",".ins":"application/x-internet-signup",".ips":"application/x-ipscript",".ipx":"application/x-ipix",".isp":"application/x-internet-signup",".it":"audio/x-mod",".itz":"audio/x-mod",".ivr":"i-world/i-vrml",".j2k":"image/j2k",".jam":"application/x-jam",".java":"text/plain",".jfif":"image/pipeg",".jpz":"image/jpeg",".jwc":"application/jwc",".kjx":"application/x-kjx",".lak":"x-lml/x-lak",".lcc":"application/fastman",".lcl":"application/x-digitalloca",".lcr":"application/x-digitalloca",".lgh":"application/lgh",".lml":"x-lml/x-lml",".lmlpack":"x-lml/x-lmlpack",".log":"text/plain",".lsf":"video/x-la-asf",".lsx":"video/x-la-asf",".m13":"application/x-msmediaview",".m14":"application/x-msmediaview",".m15":"audio/x-mod",".m3url":"audio/x-mpegurl",".m4b":"audio/mp4a-latm",".ma1":"audio/ma1",".ma2":"audio/ma2",".ma3":"audio/ma3",".ma5":"audio/ma5",".map":"magnus-internal/imagemap",".mbd":"application/mbedlet",".mct":"application/x-mascot",".mdb":"application/x-msaccess",".mdz":"audio/x-mod",".mel":"text/x-vmel",".mht":"message/rfc822",".mhtml":"message/rfc822",".mi":"application/x-mif",".mil":"image/x-cals",".mio":"audio/x-mio",".mmf":"application/x-skt-lbs",".mng":"video/x-mng",".mny":"application/x-msmoney",".moc":"application/x-mocha",".mocha":"application/x-mocha",".mod":"audio/x-mod",".mof":"application/x-yumekara",".mol":"chemical/x-mdl-molfile",".mop":"chemical/x-mopac-input",".mpa":"video/mpeg",".mpc":"application/vnd.mpohun.certificate",".mpg4":"video/mp4",".mpn":"application/vnd.mophun.application",".mpp":"application/vnd.ms-project",".mps":"application/x-mapserver",".mpv2":"video/mpeg",".mrl":"text/x-mrml",".mrm":"application/x-mrm",".msg":"application/vnd.ms-outlook",".mts":"application/metastream",".mtx":"application/metastream",".mtz":"application/metastream",".mvb":"application/x-msmediaview",".mzv":"application/metastream",".nar":"application/zip",".nbmp":"image/nbmp",".ndb":"x-lml/x-ndb",".ndwn":"application/ndwn",".nif":"application/x-nif",".nmz":"application/x-scream",".nokia-op-logo":"image/vnd.nok-oplogo-color",".npx":"application/x-netfpx",".nsnd":"audio/nsnd",".nva":"application/x-neva1",".nws":"message/rfc822",".oom":"application/x-AtlasMate-Plugin",".p10":"application/pkcs10",".p12":"application/x-pkcs12",".p7b":"application/x-pkcs7-certificates",".p7c":"application/x-pkcs7-mime",".p7m":"application/x-pkcs7-mime",".p7r":"application/x-pkcs7-certreqresp",".p7s":"application/x-pkcs7-signature",".pac":"audio/x-pac",".pae":"audio/x-epac",".pan":"application/x-pan",".pcx":"image/x-pcx",".pda":"image/x-pda",".pfr":"application/font-tdpfr",".pfx":"application/x-pkcs12",".pko":"application/ynd.ms-pkipko",".pm":"application/x-perl",".pma":"application/x-perfmon",".pmc":"application/x-perfmon",".pmd":"application/x-pmd",".pml":"application/x-perfmon",".pmr":"application/x-perfmon",".pmw":"application/x-perfmon",".pnz":"image/png",".pot,":"application/vnd.ms-powerpoint",".pps":"application/vnd.ms-powerpoint",".pqf":"application/x-cprplayer",".pqi":"application/cprplayer",".prc":"application/x-prc",".prf":"application/pics-rules",".prop":"text/plain",".proxy":"application/x-ns-proxy-autoconfig",".ptlk":"application/listenup",".pub":"application/x-mspublisher",".pvx":"video/x-pv-pvx",".qcp":"audio/vnd.qcelp",".r3t":"text/vnd.rn-realtext3d",".rar":"application/octet-stream",".rc":"text/plain",".rf":"image/vnd.rn-realflash",".rlf":"application/x-richlink",".rmf":"audio/x-rmf",".rmi":"audio/mid",".rmm":"audio/x-pn-realaudio",".rmvb":"audio/x-pn-realaudio",".rnx":"application/vnd.rn-realplayer",".rp":"image/vnd.rn-realpix",".rt":"text/vnd.rn-realtext",".rte":"x-lml/x-gps",".rtg":"application/metastream",".rv":"video/vnd.rn-realvideo",".rwc":"application/x-rogerwilco",".s3m":"audio/x-mod",".s3z":"audio/x-mod",".sca":"application/x-supercard",".scd":"application/x-msschedule",".sct":"text/scriptlet",".sdf":"application/e-score",".sea":"application/x-stuffit",".setpay":"application/set-payment_old-initiation",".setreg":"application/set-registration-initiation",".shtml":"text/html",".shtm":"text/html",".shw":"application/presentations",".si6":"image/si6",".si7":"image/vnd.stiwap.sis",".si9":"image/vnd.lgtwap.sis",".slc":"application/x-salsa",".smd":"audio/x-smd",".smp":"application/studiom",".smz":"audio/x-smd",".spc":"application/x-pkcs7-certificates",".spr":"application/x-sprite",".sprite":"application/x-sprite",".sdp":"application/sdp",".spt":"application/x-spt",".sst":"application/vnd.ms-pkicertstore",".stk":"application/hyperstudio",".stl":"application/vnd.ms-pkistl",".stm":"text/html",".svf":"image/vnd",".svh":"image/svh",".svr":"x-world/x-svr",".swfl":"application/x-shockwave-flash",".tad":"application/octet-stream",".talk":"text/x-speech",".taz":"application/x-tar",".tbp":"application/x-timbuktu",".tbt":"application/x-timbuktu",".tgz":"application/x-compressed",".thm":"application/vnd.eri.thm",".tki":"application/x-tkined",".tkined":"application/x-tkined",".toc":"application/toc",".toy":"image/toy",".trk":"x-lml/x-gps",".trm":"application/x-msterminal",".tsi":"audio/tsplayer",".tsp":"application/dsptype",".ttf":"application/octet-stream",".ttz":"application/t-time",".uls":"text/iuls",".ult":"audio/x-mod",".uu":"application/x-uuencode",".uue":"application/x-uuencode",".vcf":"text/x-vcard",".vdo":"video/vdo",".vib":"audio/vib",".viv":"video/vivo",".vivo":"video/vivo",".vmd":"application/vocaltec-media-desc",".vmf":"application/vocaltec-media-file",".vmi":"application/x-dreamcast-vms-info",".vms":"application/x-dreamcast-vms",".vox":"audio/voxware",".vqe":"audio/x-twinvq-plugin",".vqf":"audio/x-twinvq",".vql":"audio/x-twinvq",".vre":"x-world/x-vream",".vrt":"x-world/x-vrt",".vrw":"x-world/x-vream",".vts":"workbook/formulaone",".wcm":"application/vnd.ms-works",".wdb":"application/vnd.ms-works",".web":"application/vnd.xara",".wi":"image/wavelet",".wis":"application/x-InstallShield",".wks":"application/vnd.ms-works",".wmd":"application/x-ms-wmd",".wmf":"application/x-msmetafile",".wmlscript":"text/vnd.wap.wmlscript",".wmz":"application/x-ms-wmz",".wpng":"image/x-up-wpng",".wps":"application/vnd.ms-works",".wpt":"x-lml/x-gps",".wri":"application/x-mswrite",".wrz":"x-world/x-vrml",".ws":"text/vnd.wap.wmlscript",".wsc":"application/vnd.wap.wmlscriptc",".wv":"video/wavelet",".wxl":"application/x-wxl",".x-gzip":"application/x-gzip",".xaf":"x-world/x-vrml",".xar":"application/vnd.xara",".xdm":"application/x-xdma",".xdma":"application/x-xdma",".xdw":"application/vnd.fujixerox.docuworks",".xhtm":"application/xhtml+xml",".xla":"application/vnd.ms-excel",".xlc":"application/vnd.ms-excel",".xll":"application/x-excel",".xlm":"application/vnd.ms-excel",".xlt":"application/vnd.ms-excel",".xlw":"application/vnd.ms-excel",".xm":"audio/x-mod",".xmz":"audio/x-mod",".xof":"x-world/x-vrml",".xpi":"application/x-xpinstall",".xsit":"text/xml",".yz1":"application/x-yz1",".z":"application/x-compress",".zac":"application/x-zaurus-zac",".json":"application/json"}`, - "logto_enabled": "0", - "logto_config": `{"direct_sign_in":true,"display_name":"vas.sso"}`, - "qq_login": `0`, - "qq_login_config": `{"direct_sign_in":false}`, - "license": "", - "custom_nav_items": "[]", - "headless_footer_html": "", - "headless_bottom_html": "", - "sidebar_bottom_html": "", - "encrypt_master_key": "", - "encrypt_master_key_vault": "setting", - "encrypt_master_key_file": "", - "show_encryption_status": "1", - "show_desktop_app_promotion": "1", - "fs_event_push_enabled": "1", - "fs_event_push_max_age": "1209600", - "fs_event_push_debounce": "5", - "fts_enabled": "0", - "fts_index_type": "meilisearch", - "fts_extractor_type": "tika", - "fts_meilisearch_endpoint": "", - "fts_meilisearch_api_key": "", - "fts_meilisearch_page_size": "5", - "fts_meilisearch_embed_enabled": "0", - "fts_meilisearch_embed_config": "{}", - "fts_tika_endpoint": "", - "fts_tika_exts": "pdf,doc,docx,xls,xlsx,ppt,pptx,odt,ods,odp,rtf,txt,md,html,htm,epub,csv", - "fts_tika_max_file_size": "26214400", - "fts_chunk_size": "2000", - "viewer_default_apps": "{}", - "expose_user_email": "1", + "media_compress_image_enabled": "0", + "media_compress_engine": "vips", + "media_compress_worker_num": "1", + "media_compress_batch_size": "50", + "media_compress_image_quality": "80", + "media_compress_image_format": "keep", + "media_compress_image_args": "", + "media_compress_result_mode": "version", + "media_compress_min_size": "204800", // 200 KB + // APP-103 โ€” deferred video transcoding. Runs on its own dedicated queue + // (queue_media_process_video_*) so a long transcode never blocks image + // compression. Engine is always ffmpeg (vips does not process video). + // Defaults are CPU-bounded: 1 worker, 1 thread, low priority (nice), high + // task timeout (2h). Container "keep" avoids renaming the file on transcode. + "queue_media_process_video_worker_num": "1", + "queue_media_process_video_max_execution": "7200", + "queue_media_process_video_backoff_factor": "2", + "queue_media_process_video_backoff_max_duration": "60", + "queue_media_process_video_max_retry": "3", + "queue_media_process_video_retry_delay": "5", + "media_compress_video_enabled": "0", + "media_compress_video_codec": "libx264", + "media_compress_video_crf": "28", + "media_compress_video_preset": "medium", + "media_compress_video_container": "keep", + "media_compress_video_max_resolution": "1920x1080", + "media_compress_video_audio_codec": "aac", + "media_compress_video_audio_bitrate": "128k", + "media_compress_video_args": "", + "media_compress_video_worker_num": "1", + "media_compress_video_batch_size": "10", + "media_compress_video_threads": "1", + "media_compress_video_nice": "1", + "media_compress_video_min_size": "10485760", // 10 MB + "entity_url_default_ttl": "3600", + "entity_url_cache_margin": "600", + "media_meta": "1", + "media_meta_exif": "1", + "media_meta_exif_size_local": "1073741824", + "media_meta_exif_size_remote": "104857600", + "media_meta_exif_brute_force": "1", + "media_meta_music": "1", + "media_meta_music_size_local": "1073741824", + "media_exif_music_size_remote": "1073741824", + "media_meta_ffprobe": "0", + "media_meta_ffprobe_path": "ffprobe", + "media_meta_ffprobe_size_local": "0", + "media_meta_ffprobe_size_remote": "0", + "media_meta_geocoding": "0", + "media_meta_geocoding_mapbox_ak": "", + "site_logo": "/static/img/logo.svg", + "site_logo_light": "/static/img/logo_light.svg", + "tos_url": "https://cloudreve.org/privacy-policy", + "privacy_policy_url": "https://cloudreve.org/privacy-policy", + "explorer_category_image_query": "type=file&case_folding&use_or&name=*.bmp&name=*.iff&name=*.png&name=*.gif&name=*.jpg&name=*.jpeg&name=*.psd&name=*.svg&name=*.webp&name=*.heif&name=*.heic&name=*.tiff&name=*.avif&name=*.3fr&name=*.ari&name=*.arw&name=*.bay&name=*.braw&name=*.crw&name=*.cr2&name=*.cr3&name=*.cap&name=*.dcs&name=*.dcr&name=*.dng&name=*.drf&name=*.eip&name=*.erf&name=*.fff&name=*.gpr&name=*.iiq&name=*.k25&name=*.kdc&name=*.mdc&name=*.mef&name=*.mos&name=*.mrw&name=*.nef&name=*.nrw&name=*.obm&name=*.orf&name=*.pef&name=*.ptx&name=*.pxn&name=*.r3d&name=*.raf&name=*.raw&name=*.rwl&name=*.rw2&name=*.rwz&name=*.sr2&name=*.srf&name=*.srw&name=*.tif&name=*.x3f", + "explorer_category_video_query": "type=file&case_folding&use_or&name=*.mp4&name=*.m3u8&name=*.flv&name=*.avi&name=*.wmv&name=*.mkv&name=*.rm&name=*.rmvb&name=*.mov&name=*.ogv", + "explorer_category_audio_query": "type=file&case_folding&use_or&name=*.mp3&name=*.flac&name=*.ape&name=*.wav&name=*.acc&name=*.ogg&name=*.m4a", + "explorer_category_document_query": "type=file&case_folding&use_or&name=*.pdf&name=*.doc&name=*.docx&name=*.ppt&name=*.pptx&name=*.xls&name=*.xlsx&name=*.csv&name=*.txt&name=*.md&name=*.pub", + "use_sse_for_search": "0", + "emojis": `{"๐Ÿ˜€":["๐Ÿ˜€","๐Ÿ˜ƒ","๐Ÿ˜„","๐Ÿ˜","๐Ÿ˜†","๐Ÿ˜…","๐Ÿคฃ","๐Ÿ˜‚","๐Ÿ™‚","๐Ÿ™ƒ","๐Ÿซ ","๐Ÿ˜‰","๐Ÿ˜Š","๐Ÿ˜‡","๐Ÿฅฐ","๐Ÿ˜","๐Ÿคฉ","๐Ÿ˜˜","๐Ÿ˜—","๐Ÿ˜š","๐Ÿ˜™","๐Ÿฅฒ","๐Ÿ˜‹","๐Ÿ˜›","๐Ÿ˜œ","๐Ÿคช","๐Ÿ˜","๐Ÿค‘","๐Ÿค—","๐Ÿคญ","๐Ÿซข","๐Ÿซฃ","๐Ÿคซ","๐Ÿค”","๐Ÿซก","๐Ÿค","๐Ÿคจ","๐Ÿ˜","๐Ÿ˜‘","๐Ÿ˜ถ","๐Ÿ˜ถโ€๐ŸŒซ๏ธ","๐Ÿ˜","๐Ÿ˜’","๐Ÿ™„","๐Ÿ˜ฌ","๐Ÿ˜ฎโ€๐Ÿ’จ","๐Ÿคฅ","๐Ÿ˜Œ","๐Ÿ˜”","๐Ÿ˜ช","๐Ÿคค","๐Ÿ˜ด","๐Ÿ˜ท","๐Ÿค’","๐Ÿค•","๐Ÿคข","๐Ÿคฎ","๐Ÿคง","๐Ÿฅต","๐Ÿฅถ","๐Ÿฅด","๐Ÿ˜ต","๐Ÿ˜ตโ€๐Ÿ’ซ","๐Ÿคฏ","๐Ÿค ","๐Ÿฅณ","๐Ÿฅธ","๐Ÿ˜Ž","๐Ÿค“","๐Ÿง","๐Ÿ˜•","๐Ÿซค","๐Ÿ˜Ÿ","๐Ÿ™","๐Ÿ˜ฎ","๐Ÿ˜ฏ","๐Ÿ˜ฒ","๐Ÿ˜ณ","๐Ÿฅบ","๐Ÿฅน","๐Ÿ˜ฆ","๐Ÿ˜ง","๐Ÿ˜จ","๐Ÿ˜ฐ","๐Ÿ˜ฅ","๐Ÿ˜ข","๐Ÿ˜ญ","๐Ÿ˜ฑ","๐Ÿ˜–","๐Ÿ˜ฃ","๐Ÿ˜ž","๐Ÿ˜“","๐Ÿ˜ฉ","๐Ÿ˜ซ","๐Ÿฅฑ","๐Ÿ˜ค","๐Ÿ˜ก","๐Ÿ˜ ","๐Ÿคฌ","๐Ÿ˜ˆ","๐Ÿ‘ฟ","๐Ÿ’€","โ˜ ๏ธ","๐Ÿ’ฉ","๐Ÿคก","๐Ÿ‘น","๐Ÿ‘บ","๐Ÿ‘ป","๐Ÿ‘ฝ","๐Ÿ‘พ","๐Ÿค–","๐Ÿ˜บ","๐Ÿ˜ธ","๐Ÿ˜น","๐Ÿ˜ป","๐Ÿ˜ผ","๐Ÿ˜ฝ","๐Ÿ™€","๐Ÿ˜ฟ","๐Ÿ˜พ","๐Ÿ™ˆ","๐Ÿ™‰","๐Ÿ™Š","๐Ÿ’‹","๐Ÿ’Œ","๐Ÿ’˜","๐Ÿ’","๐Ÿ’–","๐Ÿ’—","๐Ÿ’“","๐Ÿ’ž","๐Ÿ’•","๐Ÿ’Ÿ","๐Ÿ’”","โค๏ธโ€๐Ÿ”ฅ","โค๏ธโ€๐Ÿฉน","โค๏ธ","๐Ÿงก","๐Ÿ’›","๐Ÿ’š","๐Ÿ’™","๐Ÿ’œ","๐ŸคŽ","๐Ÿ–ค","๐Ÿค","๐Ÿ’ฏ","๐Ÿ’ข","๐Ÿ’ฅ","๐Ÿ’ซ","๐Ÿ’ฆ","๐Ÿ’จ","๐Ÿ•ณ๏ธ","๐Ÿ’ฃ","๐Ÿ’ฌ","๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ","๐Ÿ—จ๏ธ","๐Ÿ—ฏ๏ธ","๐Ÿ’ญ","๐Ÿ’ค"],"๐Ÿ‘‹":["๐Ÿ‘‹","๐Ÿคš","๐Ÿ–๏ธ","โœ‹","๐Ÿ––","๐Ÿซฑ","๐Ÿซฒ","๐Ÿซณ","๐Ÿซด","๐Ÿ‘Œ","๐ŸคŒ","๐Ÿค","โœŒ๏ธ","๐Ÿคž","๐Ÿซฐ","๐ŸคŸ","๐Ÿค˜","๐Ÿค™","๐Ÿ‘ˆ","๐Ÿ‘‰","๐Ÿ‘†","๐Ÿ–•","๐Ÿ‘‡","โ˜๏ธ","๐Ÿซต","๐Ÿ‘","๐Ÿ‘Ž","โœŠ","๐Ÿ‘Š","๐Ÿค›","๐Ÿคœ","๐Ÿ‘","๐Ÿ™Œ","๐Ÿซถ","๐Ÿ‘","๐Ÿคฒ","๐Ÿค","๐Ÿ™","โœ๏ธ","๐Ÿ’…","๐Ÿคณ","๐Ÿ’ช","๐Ÿฆพ","๐Ÿฆฟ","๐Ÿฆต","๐Ÿฆถ","๐Ÿ‘‚","๐Ÿฆป","๐Ÿ‘ƒ","๐Ÿง ","๐Ÿซ€","๐Ÿซ","๐Ÿฆท","๐Ÿฆด","๐Ÿ‘€","๐Ÿ‘๏ธ","๐Ÿ‘…","๐Ÿ‘„","๐Ÿซฆ","๐Ÿ‘ถ","๐Ÿง’","๐Ÿ‘ฆ","๐Ÿ‘ง","๐Ÿง‘","๐Ÿ‘ฑ","๐Ÿ‘จ","๐Ÿง”","๐Ÿง”โ€โ™‚๏ธ","๐Ÿง”โ€โ™€๏ธ","๐Ÿ‘จโ€๐Ÿฆฐ","๐Ÿ‘จโ€๐Ÿฆฑ","๐Ÿ‘จโ€๐Ÿฆณ","๐Ÿ‘จโ€๐Ÿฆฒ","๐Ÿ‘ฉ","๐Ÿ‘ฉโ€๐Ÿฆฐ","๐Ÿง‘โ€๐Ÿฆฐ","๐Ÿ‘ฉโ€๐Ÿฆฑ","๐Ÿง‘โ€๐Ÿฆฑ","๐Ÿ‘ฉโ€๐Ÿฆณ","๐Ÿง‘โ€๐Ÿฆณ","๐Ÿ‘ฉโ€๐Ÿฆฒ","๐Ÿง‘โ€๐Ÿฆฒ","๐Ÿ‘ฑโ€โ™€๏ธ","๐Ÿ‘ฑโ€โ™‚๏ธ","๐Ÿง“","๐Ÿ‘ด","๐Ÿ‘ต","๐Ÿ™","๐Ÿ™โ€โ™‚๏ธ","๐Ÿ™โ€โ™€๏ธ","๐Ÿ™Ž","๐Ÿ™Žโ€โ™‚๏ธ","๐Ÿ™Žโ€โ™€๏ธ","๐Ÿ™…","๐Ÿ™…โ€โ™‚๏ธ","๐Ÿ™…โ€โ™€๏ธ","๐Ÿ™†","๐Ÿ™†โ€โ™‚๏ธ","๐Ÿ™†โ€โ™€๏ธ","๐Ÿ’","๐Ÿ’โ€โ™‚๏ธ","๐Ÿ’โ€โ™€๏ธ","๐Ÿ™‹","๐Ÿ™‹โ€โ™‚๏ธ","๐Ÿ™‹โ€โ™€๏ธ","๐Ÿง","๐Ÿงโ€โ™‚๏ธ","๐Ÿงโ€โ™€๏ธ","๐Ÿ™‡","๐Ÿ™‡โ€โ™‚๏ธ","๐Ÿ™‡โ€โ™€๏ธ","๐Ÿคฆ","๐Ÿคฆโ€โ™‚๏ธ","๐Ÿคฆโ€โ™€๏ธ","๐Ÿคท","๐Ÿคทโ€โ™‚๏ธ","๐Ÿคทโ€โ™€๏ธ","๐Ÿง‘โ€โš•๏ธ","๐Ÿ‘จโ€โš•๏ธ","๐Ÿ‘ฉโ€โš•๏ธ","๐Ÿง‘โ€๐ŸŽ“","๐Ÿ‘จโ€๐ŸŽ“","๐Ÿ‘ฉโ€๐ŸŽ“","๐Ÿง‘โ€๐Ÿซ","๐Ÿ‘จโ€๐Ÿซ","๐Ÿ‘ฉโ€๐Ÿซ","๐Ÿง‘โ€โš–๏ธ","๐Ÿ‘จโ€โš–๏ธ","๐Ÿ‘ฉโ€โš–๏ธ","๐Ÿง‘โ€๐ŸŒพ","๐Ÿ‘จโ€๐ŸŒพ","๐Ÿ‘ฉโ€๐ŸŒพ","๐Ÿง‘โ€๐Ÿณ","๐Ÿ‘จโ€๐Ÿณ","๐Ÿ‘ฉโ€๐Ÿณ","๐Ÿง‘โ€๐Ÿ”ง","๐Ÿ‘จโ€๐Ÿ”ง","๐Ÿ‘ฉโ€๐Ÿ”ง","๐Ÿง‘โ€๐Ÿญ","๐Ÿ‘จโ€๐Ÿญ","๐Ÿ‘ฉโ€๐Ÿญ","๐Ÿง‘โ€๐Ÿ’ผ","๐Ÿ‘จโ€๐Ÿ’ผ","๐Ÿ‘ฉโ€๐Ÿ’ผ","๐Ÿง‘โ€๐Ÿ”ฌ","๐Ÿ‘จโ€๐Ÿ”ฌ","๐Ÿ‘ฉโ€๐Ÿ”ฌ","๐Ÿง‘โ€๐Ÿ’ป","๐Ÿ‘จโ€๐Ÿ’ป","๐Ÿ‘ฉโ€๐Ÿ’ป","๐Ÿง‘โ€๐ŸŽค","๐Ÿ‘จโ€๐ŸŽค","๐Ÿ‘ฉโ€๐ŸŽค","๐Ÿง‘โ€๐ŸŽจ","๐Ÿ‘จโ€๐ŸŽจ","๐Ÿ‘ฉโ€๐ŸŽจ","๐Ÿง‘โ€โœˆ๏ธ","๐Ÿ‘จโ€โœˆ๏ธ","๐Ÿ‘ฉโ€โœˆ๏ธ","๐Ÿง‘โ€๐Ÿš€","๐Ÿ‘จโ€๐Ÿš€","๐Ÿ‘ฉโ€๐Ÿš€","๐Ÿง‘โ€๐Ÿš’","๐Ÿ‘จโ€๐Ÿš’","๐Ÿ‘ฉโ€๐Ÿš’","๐Ÿ‘ฎ","๐Ÿ‘ฎโ€โ™‚๏ธ","๐Ÿ‘ฎโ€โ™€๏ธ","๐Ÿ•ต๏ธ","๐Ÿ•ต๏ธโ€โ™‚๏ธ","๐Ÿ•ต๏ธโ€โ™€๏ธ","๐Ÿ’‚","๐Ÿ’‚โ€โ™‚๏ธ","๐Ÿ’‚โ€โ™€๏ธ","๐Ÿฅท","๐Ÿ‘ท","๐Ÿ‘ทโ€โ™‚๏ธ","๐Ÿ‘ทโ€โ™€๏ธ","๐Ÿซ…","๐Ÿคด","๐Ÿ‘ธ","๐Ÿ‘ณ","๐Ÿ‘ณโ€โ™‚๏ธ","๐Ÿ‘ณโ€โ™€๏ธ","๐Ÿ‘ฒ","๐Ÿง•","๐Ÿคต","๐Ÿคตโ€โ™‚๏ธ","๐Ÿคตโ€โ™€๏ธ","๐Ÿ‘ฐ","๐Ÿ‘ฐโ€โ™‚๏ธ","๐Ÿ‘ฐโ€โ™€๏ธ","๐Ÿคฐ","๐Ÿซƒ","๐Ÿซ„","๐Ÿคฑ","๐Ÿ‘ฉโ€๐Ÿผ","๐Ÿ‘จโ€๐Ÿผ","๐Ÿง‘โ€๐Ÿผ","๐Ÿ‘ผ","๐ŸŽ…","๐Ÿคถ","๐Ÿง‘โ€๐ŸŽ„","๐Ÿฆธ","๐Ÿฆธโ€โ™‚๏ธ","๐Ÿฆธโ€โ™€๏ธ","๐Ÿฆน","๐Ÿฆนโ€โ™‚๏ธ","๐Ÿฆนโ€โ™€๏ธ","๐Ÿง™","๐Ÿง™โ€โ™‚๏ธ","๐Ÿง™โ€โ™€๏ธ","๐Ÿงš","๐Ÿงšโ€โ™‚๏ธ","๐Ÿงšโ€โ™€๏ธ","๐Ÿง›","๐Ÿง›โ€โ™‚๏ธ","๐Ÿง›โ€โ™€๏ธ","๐Ÿงœ","๐Ÿงœโ€โ™‚๏ธ","๐Ÿงœโ€โ™€๏ธ","๐Ÿง","๐Ÿงโ€โ™‚๏ธ","๐Ÿงโ€โ™€๏ธ","๐Ÿงž","๐Ÿงžโ€โ™‚๏ธ","๐Ÿงžโ€โ™€๏ธ","๐ŸงŸ","๐ŸงŸโ€โ™‚๏ธ","๐ŸงŸโ€โ™€๏ธ","๐ŸงŒ","๐Ÿ’†","๐Ÿ’†โ€โ™‚๏ธ","๐Ÿ’†โ€โ™€๏ธ","๐Ÿ’‡","๐Ÿ’‡โ€โ™‚๏ธ","๐Ÿ’‡โ€โ™€๏ธ","๐Ÿšถ","๐Ÿšถโ€โ™‚๏ธ","๐Ÿšถโ€โ™€๏ธ","๐Ÿง","๐Ÿงโ€โ™‚๏ธ","๐Ÿงโ€โ™€๏ธ","๐ŸงŽ","๐ŸงŽโ€โ™‚๏ธ","๐ŸงŽโ€โ™€๏ธ","๐Ÿง‘โ€๐Ÿฆฏ","๐Ÿ‘จโ€๐Ÿฆฏ","๐Ÿ‘ฉโ€๐Ÿฆฏ","๐Ÿง‘โ€๐Ÿฆผ","๐Ÿ‘จโ€๐Ÿฆผ","๐Ÿ‘ฉโ€๐Ÿฆผ","๐Ÿง‘โ€๐Ÿฆฝ","๐Ÿ‘จโ€๐Ÿฆฝ","๐Ÿ‘ฉโ€๐Ÿฆฝ","๐Ÿƒ","๐Ÿƒโ€โ™‚๏ธ","๐Ÿƒโ€โ™€๏ธ","๐Ÿ’ƒ","๐Ÿ•บ","๐Ÿ•ด๏ธ","๐Ÿ‘ฏ","๐Ÿ‘ฏโ€โ™‚๏ธ","๐Ÿ‘ฏโ€โ™€๏ธ","๐Ÿง–","๐Ÿง–โ€โ™‚๏ธ","๐Ÿง–โ€โ™€๏ธ","๐Ÿง—","๐Ÿง—โ€โ™‚๏ธ","๐Ÿง—โ€โ™€๏ธ","๐Ÿคบ","๐Ÿ‡","โ›ท๏ธ","๐Ÿ‚","๐ŸŒ๏ธ","๐ŸŒ๏ธโ€โ™‚๏ธ","๐ŸŒ๏ธโ€โ™€๏ธ","๐Ÿ„","๐Ÿ„โ€โ™‚๏ธ","๐Ÿ„โ€โ™€๏ธ","๐Ÿšฃ","๐Ÿšฃโ€โ™‚๏ธ","๐Ÿšฃโ€โ™€๏ธ","๐ŸŠ","๐ŸŠโ€โ™‚๏ธ","๐ŸŠโ€โ™€๏ธ","โ›น๏ธ","โ›น๏ธโ€โ™‚๏ธ","โ›น๏ธโ€โ™€๏ธ","๐Ÿ‹๏ธ","๐Ÿ‹๏ธโ€โ™‚๏ธ","๐Ÿ‹๏ธโ€โ™€๏ธ","๐Ÿšด","๐Ÿšดโ€โ™‚๏ธ","๐Ÿšดโ€โ™€๏ธ","๐Ÿšต","๐Ÿšตโ€โ™‚๏ธ","๐Ÿšตโ€โ™€๏ธ","๐Ÿคธ","๐Ÿคธโ€โ™‚๏ธ","๐Ÿคธโ€โ™€๏ธ","๐Ÿคผ","๐Ÿคผโ€โ™‚๏ธ","๐Ÿคผโ€โ™€๏ธ","๐Ÿคฝ","๐Ÿคฝโ€โ™‚๏ธ","๐Ÿคฝโ€โ™€๏ธ","๐Ÿคพ","๐Ÿคพโ€โ™‚๏ธ","๐Ÿคพโ€โ™€๏ธ","๐Ÿคน","๐Ÿคนโ€โ™‚๏ธ","๐Ÿคนโ€โ™€๏ธ","๐Ÿง˜","๐Ÿง˜โ€โ™‚๏ธ","๐Ÿง˜โ€โ™€๏ธ","๐Ÿ›€","๐Ÿ›Œ","๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘","๐Ÿ‘ญ","๐Ÿ‘ซ","๐Ÿ‘ฌ","๐Ÿ’","๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ","๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ","๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ","๐Ÿ’‘","๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘จ","๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ","๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ","๐Ÿ‘ช","๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง","๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง","๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง","๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ","๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ง","๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","๐Ÿ‘จโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘ง","๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง","๐Ÿ‘ฉโ€๐Ÿ‘ฆ","๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","๐Ÿ‘ฉโ€๐Ÿ‘ง","๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","๐Ÿ—ฃ๏ธ","๐Ÿ‘ค","๐Ÿ‘ฅ","๐Ÿซ‚","๐Ÿ‘ฃ","๐Ÿฆฐ","๐Ÿฆฑ","๐Ÿฆณ","๐Ÿฆฒ"],"๐Ÿต":["๐Ÿต","๐Ÿ’","๐Ÿฆ","๐Ÿฆง","๐Ÿถ","๐Ÿ•","๐Ÿฆฎ","๐Ÿ•โ€๐Ÿฆบ","๐Ÿฉ","๐Ÿบ","๐ŸฆŠ","๐Ÿฆ","๐Ÿฑ","๐Ÿˆ","๐Ÿˆโ€โฌ›","๐Ÿฆ","๐Ÿฏ","๐Ÿ…","๐Ÿ†","๐Ÿด","๐ŸŽ","๐Ÿฆ„","๐Ÿฆ“","๐ŸฆŒ","๐Ÿฆฌ","๐Ÿฎ","๐Ÿ‚","๐Ÿƒ","๐Ÿ„","๐Ÿท","๐Ÿ–","๐Ÿ—","๐Ÿฝ","๐Ÿ","๐Ÿ‘","๐Ÿ","๐Ÿช","๐Ÿซ","๐Ÿฆ™","๐Ÿฆ’","๐Ÿ˜","๐Ÿฆฃ","๐Ÿฆ","๐Ÿฆ›","๐Ÿญ","๐Ÿ","๐Ÿ€","๐Ÿน","๐Ÿฐ","๐Ÿ‡","๐Ÿฟ๏ธ","๐Ÿฆซ","๐Ÿฆ”","๐Ÿฆ‡","๐Ÿป","๐Ÿปโ€โ„๏ธ","๐Ÿจ","๐Ÿผ","๐Ÿฆฅ","๐Ÿฆฆ","๐Ÿฆจ","๐Ÿฆ˜","๐Ÿฆก","๐Ÿพ","๐Ÿฆƒ","๐Ÿ”","๐Ÿ“","๐Ÿฃ","๐Ÿค","๐Ÿฅ","๐Ÿฆ","๐Ÿง","๐Ÿ•Š๏ธ","๐Ÿฆ…","๐Ÿฆ†","๐Ÿฆข","๐Ÿฆ‰","๐Ÿฆค","๐Ÿชถ","๐Ÿฆฉ","๐Ÿฆš","๐Ÿฆœ","๐Ÿธ","๐ŸŠ","๐Ÿข","๐ŸฆŽ","๐Ÿ","๐Ÿฒ","๐Ÿ‰","๐Ÿฆ•","๐Ÿฆ–","๐Ÿณ","๐Ÿ‹","๐Ÿฌ","๐Ÿฆญ","๐ŸŸ","๐Ÿ ","๐Ÿก","๐Ÿฆˆ","๐Ÿ™","๐Ÿš","๐Ÿชธ","๐ŸŒ","๐Ÿฆ‹","๐Ÿ›","๐Ÿœ","๐Ÿ","๐Ÿชฒ","๐Ÿž","๐Ÿฆ—","๐Ÿชณ","๐Ÿ•ท๏ธ","๐Ÿ•ธ๏ธ","๐Ÿฆ‚","๐ŸฆŸ","๐Ÿชฐ","๐Ÿชฑ","๐Ÿฆ ","๐Ÿ’","๐ŸŒธ","๐Ÿ’ฎ","๐Ÿชท","๐Ÿต๏ธ","๐ŸŒน","๐Ÿฅ€","๐ŸŒบ","๐ŸŒป","๐ŸŒผ","๐ŸŒท","๐ŸŒฑ","๐Ÿชด","๐ŸŒฒ","๐ŸŒณ","๐ŸŒด","๐ŸŒต","๐ŸŒพ","๐ŸŒฟ","โ˜˜๏ธ","๐Ÿ€","๐Ÿ","๐Ÿ‚","๐Ÿƒ","๐Ÿชน","๐Ÿชบ"],"๐Ÿ‡":["๐Ÿ‡","๐Ÿˆ","๐Ÿ‰","๐ŸŠ","๐Ÿ‹","๐ŸŒ","๐Ÿ","๐Ÿฅญ","๐ŸŽ","๐Ÿ","๐Ÿ","๐Ÿ‘","๐Ÿ’","๐Ÿ“","๐Ÿซ","๐Ÿฅ","๐Ÿ…","๐Ÿซ’","๐Ÿฅฅ","๐Ÿฅ‘","๐Ÿ†","๐Ÿฅ”","๐Ÿฅ•","๐ŸŒฝ","๐ŸŒถ๏ธ","๐Ÿซ‘","๐Ÿฅ’","๐Ÿฅฌ","๐Ÿฅฆ","๐Ÿง„","๐Ÿง…","๐Ÿ„","๐Ÿฅœ","๐Ÿซ˜","๐ŸŒฐ","๐Ÿž","๐Ÿฅ","๐Ÿฅ–","๐Ÿซ“","๐Ÿฅจ","๐Ÿฅฏ","๐Ÿฅž","๐Ÿง‡","๐Ÿง€","๐Ÿ–","๐Ÿ—","๐Ÿฅฉ","๐Ÿฅ“","๐Ÿ”","๐ŸŸ","๐Ÿ•","๐ŸŒญ","๐Ÿฅช","๐ŸŒฎ","๐ŸŒฏ","๐Ÿซ”","๐Ÿฅ™","๐Ÿง†","๐Ÿฅš","๐Ÿณ","๐Ÿฅ˜","๐Ÿฒ","๐Ÿซ•","๐Ÿฅฃ","๐Ÿฅ—","๐Ÿฟ","๐Ÿงˆ","๐Ÿง‚","๐Ÿฅซ","๐Ÿฑ","๐Ÿ˜","๐Ÿ™","๐Ÿš","๐Ÿ›","๐Ÿœ","๐Ÿ","๐Ÿ ","๐Ÿข","๐Ÿฃ","๐Ÿค","๐Ÿฅ","๐Ÿฅฎ","๐Ÿก","๐ŸฅŸ","๐Ÿฅ ","๐Ÿฅก","๐Ÿฆ€","๐Ÿฆž","๐Ÿฆ","๐Ÿฆ‘","๐Ÿฆช","๐Ÿฆ","๐Ÿง","๐Ÿจ","๐Ÿฉ","๐Ÿช","๐ŸŽ‚","๐Ÿฐ","๐Ÿง","๐Ÿฅง","๐Ÿซ","๐Ÿฌ","๐Ÿญ","๐Ÿฎ","๐Ÿฏ","๐Ÿผ","๐Ÿฅ›","โ˜•","๐Ÿซ–","๐Ÿต","๐Ÿถ","๐Ÿพ","๐Ÿท","๐Ÿธ","๐Ÿน","๐Ÿบ","๐Ÿป","๐Ÿฅ‚","๐Ÿฅƒ","๐Ÿซ—","๐Ÿฅค","๐Ÿง‹","๐Ÿงƒ","๐Ÿง‰","๐ŸงŠ","๐Ÿฅข","๐Ÿฝ๏ธ","๐Ÿด","๐Ÿฅ„","๐Ÿ”ช","๐Ÿซ™","๐Ÿบ"],"๐ŸŒ":["๐ŸŒ","๐ŸŒŽ","๐ŸŒ","๐ŸŒ","๐Ÿ—บ๏ธ","๐Ÿ—พ","๐Ÿงญ","๐Ÿ”๏ธ","โ›ฐ๏ธ","๐ŸŒ‹","๐Ÿ—ป","๐Ÿ•๏ธ","๐Ÿ–๏ธ","๐Ÿœ๏ธ","๐Ÿ๏ธ","๐Ÿž๏ธ","๐ŸŸ๏ธ","๐Ÿ›๏ธ","๐Ÿ—๏ธ","๐Ÿงฑ","๐Ÿชจ","๐Ÿชต","๐Ÿ›–","๐Ÿ˜๏ธ","๐Ÿš๏ธ","๐Ÿ ","๐Ÿก","๐Ÿข","๐Ÿฃ","๐Ÿค","๐Ÿฅ","๐Ÿฆ","๐Ÿจ","๐Ÿฉ","๐Ÿช","๐Ÿซ","๐Ÿฌ","๐Ÿญ","๐Ÿฏ","๐Ÿฐ","๐Ÿ’’","๐Ÿ—ผ","๐Ÿ—ฝ","โ›ช","๐Ÿ•Œ","๐Ÿ›•","๐Ÿ•","โ›ฉ๏ธ","๐Ÿ•‹","โ›ฒ","โ›บ","๐ŸŒ","๐ŸŒƒ","๐Ÿ™๏ธ","๐ŸŒ„","๐ŸŒ…","๐ŸŒ†","๐ŸŒ‡","๐ŸŒ‰","โ™จ๏ธ","๐ŸŽ ","๐Ÿ›","๐ŸŽก","๐ŸŽข","๐Ÿ’ˆ","๐ŸŽช","๐Ÿš‚","๐Ÿšƒ","๐Ÿš„","๐Ÿš…","๐Ÿš†","๐Ÿš‡","๐Ÿšˆ","๐Ÿš‰","๐ŸšŠ","๐Ÿš","๐Ÿšž","๐Ÿš‹","๐ŸšŒ","๐Ÿš","๐ŸšŽ","๐Ÿš","๐Ÿš‘","๐Ÿš’","๐Ÿš“","๐Ÿš”","๐Ÿš•","๐Ÿš–","๐Ÿš—","๐Ÿš˜","๐Ÿš™","๐Ÿ›ป","๐Ÿšš","๐Ÿš›","๐Ÿšœ","๐ŸŽ๏ธ","๐Ÿ๏ธ","๐Ÿ›ต","๐Ÿฆฝ","๐Ÿฆผ","๐Ÿ›บ","๐Ÿšฒ","๐Ÿ›ด","๐Ÿ›น","๐Ÿ›ผ","๐Ÿš","๐Ÿ›ฃ๏ธ","๐Ÿ›ค๏ธ","๐Ÿ›ข๏ธ","โ›ฝ","๐Ÿ›ž","๐Ÿšจ","๐Ÿšฅ","๐Ÿšฆ","๐Ÿ›‘","๐Ÿšง","โš“","๐Ÿ›Ÿ","โ›ต","๐Ÿ›ถ","๐Ÿšค","๐Ÿ›ณ๏ธ","โ›ด๏ธ","๐Ÿ›ฅ๏ธ","๐Ÿšข","โœˆ๏ธ","๐Ÿ›ฉ๏ธ","๐Ÿ›ซ","๐Ÿ›ฌ","๐Ÿช‚","๐Ÿ’บ","๐Ÿš","๐ŸšŸ","๐Ÿš ","๐Ÿšก","๐Ÿ›ฐ๏ธ","๐Ÿš€","๐Ÿ›ธ","๐Ÿ›Ž๏ธ","๐Ÿงณ","โŒ›","โณ","โŒš","โฐ","โฑ๏ธ","โฒ๏ธ","๐Ÿ•ฐ๏ธ","๐Ÿ•›","๐Ÿ•ง","๐Ÿ•","๐Ÿ•œ","๐Ÿ•‘","๐Ÿ•","๐Ÿ•’","๐Ÿ•ž","๐Ÿ•“","๐Ÿ•Ÿ","๐Ÿ•”","๐Ÿ• ","๐Ÿ••","๐Ÿ•ก","๐Ÿ•–","๐Ÿ•ข","๐Ÿ•—","๐Ÿ•ฃ","๐Ÿ•˜","๐Ÿ•ค","๐Ÿ•™","๐Ÿ•ฅ","๐Ÿ•š","๐Ÿ•ฆ","๐ŸŒ‘","๐ŸŒ’","๐ŸŒ“","๐ŸŒ”","๐ŸŒ•","๐ŸŒ–","๐ŸŒ—","๐ŸŒ˜","๐ŸŒ™","๐ŸŒš","๐ŸŒ›","๐ŸŒœ","๐ŸŒก๏ธ","โ˜€๏ธ","๐ŸŒ","๐ŸŒž","๐Ÿช","โญ","๐ŸŒŸ","๐ŸŒ ","๐ŸŒŒ","โ˜๏ธ","โ›…","โ›ˆ๏ธ","๐ŸŒค๏ธ","๐ŸŒฅ๏ธ","๐ŸŒฆ๏ธ","๐ŸŒง๏ธ","๐ŸŒจ๏ธ","๐ŸŒฉ๏ธ","๐ŸŒช๏ธ","๐ŸŒซ๏ธ","๐ŸŒฌ๏ธ","๐ŸŒ€","๐ŸŒˆ","๐ŸŒ‚","โ˜‚๏ธ","โ˜”","โ›ฑ๏ธ","โšก","โ„๏ธ","โ˜ƒ๏ธ","โ›„","โ˜„๏ธ","๐Ÿ”ฅ","๐Ÿ’ง","๐ŸŒŠ"],"๐ŸŽƒ":["๐ŸŽƒ","๐ŸŽ„","๐ŸŽ†","๐ŸŽ‡","๐Ÿงจ","โœจ","๐ŸŽˆ","๐ŸŽ‰","๐ŸŽŠ","๐ŸŽ‹","๐ŸŽ","๐ŸŽŽ","๐ŸŽ","๐ŸŽ","๐ŸŽ‘","๐Ÿงง","๐ŸŽ€","๐ŸŽ","๐ŸŽ—๏ธ","๐ŸŽŸ๏ธ","๐ŸŽซ","๐ŸŽ–๏ธ","๐Ÿ†","๐Ÿ…","๐Ÿฅ‡","๐Ÿฅˆ","๐Ÿฅ‰","โšฝ","โšพ","๐ŸฅŽ","๐Ÿ€","๐Ÿ","๐Ÿˆ","๐Ÿ‰","๐ŸŽพ","๐Ÿฅ","๐ŸŽณ","๐Ÿ","๐Ÿ‘","๐Ÿ’","๐Ÿฅ","๐Ÿ“","๐Ÿธ","๐ŸฅŠ","๐Ÿฅ‹","๐Ÿฅ…","โ›ณ","โ›ธ๏ธ","๐ŸŽฃ","๐Ÿคฟ","๐ŸŽฝ","๐ŸŽฟ","๐Ÿ›ท","๐ŸฅŒ","๐ŸŽฏ","๐Ÿช€","๐Ÿช","๐ŸŽฑ","๐Ÿ”ฎ","๐Ÿช„","๐Ÿงฟ","๐Ÿชฌ","๐ŸŽฎ","๐Ÿ•น๏ธ","๐ŸŽฐ","๐ŸŽฒ","๐Ÿงฉ","๐Ÿงธ","๐Ÿช…","๐Ÿชฉ","๐Ÿช†","โ™ ๏ธ","โ™ฅ๏ธ","โ™ฆ๏ธ","โ™ฃ๏ธ","โ™Ÿ๏ธ","๐Ÿƒ","๐Ÿ€„","๐ŸŽด","๐ŸŽญ","๐Ÿ–ผ๏ธ","๐ŸŽจ","๐Ÿงต","๐Ÿชก","๐Ÿงถ","๐Ÿชข"],"๐Ÿ‘“":["๐Ÿ‘“","๐Ÿ•ถ๏ธ","๐Ÿฅฝ","๐Ÿฅผ","๐Ÿฆบ","๐Ÿ‘”","๐Ÿ‘•","๐Ÿ‘–","๐Ÿงฃ","๐Ÿงค","๐Ÿงฅ","๐Ÿงฆ","๐Ÿ‘—","๐Ÿ‘˜","๐Ÿฅป","๐Ÿฉฑ","๐Ÿฉฒ","๐Ÿฉณ","๐Ÿ‘™","๐Ÿ‘š","๐Ÿ‘›","๐Ÿ‘œ","๐Ÿ‘","๐Ÿ›๏ธ","๐ŸŽ’","๐Ÿฉด","๐Ÿ‘ž","๐Ÿ‘Ÿ","๐Ÿฅพ","๐Ÿฅฟ","๐Ÿ‘ ","๐Ÿ‘ก","๐Ÿฉฐ","๐Ÿ‘ข","๐Ÿ‘‘","๐Ÿ‘’","๐ŸŽฉ","๐ŸŽ“","๐Ÿงข","๐Ÿช–","โ›‘๏ธ","๐Ÿ“ฟ","๐Ÿ’„","๐Ÿ’","๐Ÿ’Ž","๐Ÿ”‡","๐Ÿ”ˆ","๐Ÿ”‰","๐Ÿ”Š","๐Ÿ“ข","๐Ÿ“ฃ","๐Ÿ“ฏ","๐Ÿ””","๐Ÿ”•","๐ŸŽผ","๐ŸŽต","๐ŸŽถ","๐ŸŽ™๏ธ","๐ŸŽš๏ธ","๐ŸŽ›๏ธ","๐ŸŽค","๐ŸŽง","๐Ÿ“ป","๐ŸŽท","๐Ÿช—","๐ŸŽธ","๐ŸŽน","๐ŸŽบ","๐ŸŽป","๐Ÿช•","๐Ÿฅ","๐Ÿช˜","๐Ÿ“ฑ","๐Ÿ“ฒ","โ˜Ž๏ธ","๐Ÿ“ž","๐Ÿ“Ÿ","๐Ÿ“ ","๐Ÿ”‹","๐Ÿชซ","๐Ÿ”Œ","๐Ÿ’ป","๐Ÿ–ฅ๏ธ","๐Ÿ–จ๏ธ","โŒจ๏ธ","๐Ÿ–ฑ๏ธ","๐Ÿ–ฒ๏ธ","๐Ÿ’ฝ","๐Ÿ’พ","๐Ÿ’ฟ","๐Ÿ“€","๐Ÿงฎ","๐ŸŽฅ","๐ŸŽž๏ธ","๐Ÿ“ฝ๏ธ","๐ŸŽฌ","๐Ÿ“บ","๐Ÿ“ท","๐Ÿ“ธ","๐Ÿ“น","๐Ÿ“ผ","๐Ÿ”","๐Ÿ”Ž","๐Ÿ•ฏ๏ธ","๐Ÿ’ก","๐Ÿ”ฆ","๐Ÿฎ","๐Ÿช”","๐Ÿ“”","๐Ÿ“•","๐Ÿ“–","๐Ÿ“—","๐Ÿ“˜","๐Ÿ“™","๐Ÿ“š","๐Ÿ““","๐Ÿ“’","๐Ÿ“ƒ","๐Ÿ“œ","๐Ÿ“„","๐Ÿ“ฐ","๐Ÿ—ž๏ธ","๐Ÿ“‘","๐Ÿ”–","๐Ÿท๏ธ","๐Ÿ’ฐ","๐Ÿช™","๐Ÿ’ด","๐Ÿ’ต","๐Ÿ’ถ","๐Ÿ’ท","๐Ÿ’ธ","๐Ÿ’ณ","๐Ÿงพ","๐Ÿ’น","โœ‰๏ธ","๐Ÿ“ง","๐Ÿ“จ","๐Ÿ“ฉ","๐Ÿ“ค","๐Ÿ“ฅ","๐Ÿ“ฆ","๐Ÿ“ซ","๐Ÿ“ช","๐Ÿ“ฌ","๐Ÿ“ญ","๐Ÿ“ฎ","๐Ÿ—ณ๏ธ","โœ๏ธ","โœ’๏ธ","๐Ÿ–‹๏ธ","๐Ÿ–Š๏ธ","๐Ÿ–Œ๏ธ","๐Ÿ–๏ธ","๐Ÿ“","๐Ÿ’ผ","๐Ÿ“","๐Ÿ“‚","๐Ÿ—‚๏ธ","๐Ÿ“…","๐Ÿ“†","๐Ÿ—’๏ธ","๐Ÿ—“๏ธ","๐Ÿ“‡","๐Ÿ“ˆ","๐Ÿ“‰","๐Ÿ“Š","๐Ÿ“‹","๐Ÿ“Œ","๐Ÿ“","๐Ÿ“Ž","๐Ÿ–‡๏ธ","๐Ÿ“","๐Ÿ“","โœ‚๏ธ","๐Ÿ—ƒ๏ธ","๐Ÿ—„๏ธ","๐Ÿ—‘๏ธ","๐Ÿ”’","๐Ÿ”“","๐Ÿ”","๐Ÿ”","๐Ÿ”‘","๐Ÿ—๏ธ","๐Ÿ”จ","๐Ÿช“","โ›๏ธ","โš’๏ธ","๐Ÿ› ๏ธ","๐Ÿ—ก๏ธ","โš”๏ธ","๐Ÿ”ซ","๐Ÿชƒ","๐Ÿน","๐Ÿ›ก๏ธ","๐Ÿชš","๐Ÿ”ง","๐Ÿช›","๐Ÿ”ฉ","โš™๏ธ","๐Ÿ—œ๏ธ","โš–๏ธ","๐Ÿฆฏ","๐Ÿ”—","โ›“๏ธ","๐Ÿช","๐Ÿงฐ","๐Ÿงฒ","๐Ÿชœ","โš—๏ธ","๐Ÿงช","๐Ÿงซ","๐Ÿงฌ","๐Ÿ”ฌ","๐Ÿ”ญ","๐Ÿ“ก","๐Ÿ’‰","๐Ÿฉธ","๐Ÿ’Š","๐Ÿฉน","๐Ÿฉผ","๐Ÿฉบ","๐Ÿฉป","๐Ÿšช","๐Ÿ›—","๐Ÿชž","๐ŸชŸ","๐Ÿ›๏ธ","๐Ÿ›‹๏ธ","๐Ÿช‘","๐Ÿšฝ","๐Ÿช ","๐Ÿšฟ","๐Ÿ›","๐Ÿชค","๐Ÿช’","๐Ÿงด","๐Ÿงท","๐Ÿงน","๐Ÿงบ","๐Ÿงป","๐Ÿชฃ","๐Ÿงผ","๐Ÿซง","๐Ÿชฅ","๐Ÿงฝ","๐Ÿงฏ","๐Ÿ›’","๐Ÿšฌ","โšฐ๏ธ","๐Ÿชฆ","โšฑ๏ธ","๐Ÿ—ฟ","๐Ÿชง","๐Ÿชช"],"๐Ÿง":["๐Ÿง","๐Ÿšฎ","๐Ÿšฐ","โ™ฟ","๐Ÿšน","๐Ÿšบ","๐Ÿšป","๐Ÿšผ","๐Ÿšพ","๐Ÿ›‚","๐Ÿ›ƒ","๐Ÿ›„","๐Ÿ›…","โš ๏ธ","๐Ÿšธ","โ›”","๐Ÿšซ","๐Ÿšณ","๐Ÿšญ","๐Ÿšฏ","๐Ÿšฑ","๐Ÿšท","๐Ÿ“ต","๐Ÿ”ž","โ˜ข๏ธ","โ˜ฃ๏ธ","โฌ†๏ธ","โ†—๏ธ","โžก๏ธ","โ†˜๏ธ","โฌ‡๏ธ","โ†™๏ธ","โฌ…๏ธ","โ†–๏ธ","โ†•๏ธ","โ†”๏ธ","โ†ฉ๏ธ","โ†ช๏ธ","โคด๏ธ","โคต๏ธ","๐Ÿ”ƒ","๐Ÿ”„","๐Ÿ”™","๐Ÿ”š","๐Ÿ”›","๐Ÿ”œ","๐Ÿ”","๐Ÿ›","โš›๏ธ","๐Ÿ•‰๏ธ","โœก๏ธ","โ˜ธ๏ธ","โ˜ฏ๏ธ","โœ๏ธ","โ˜ฆ๏ธ","โ˜ช๏ธ","โ˜ฎ๏ธ","๐Ÿ•Ž","๐Ÿ”ฏ","โ™ˆ","โ™‰","โ™Š","โ™‹","โ™Œ","โ™","โ™Ž","โ™","โ™","โ™‘","โ™’","โ™“","โ›Ž","๐Ÿ”€","๐Ÿ”","๐Ÿ”‚","โ–ถ๏ธ","โฉ","โญ๏ธ","โฏ๏ธ","โ—€๏ธ","โช","โฎ๏ธ","๐Ÿ”ผ","โซ","๐Ÿ”ฝ","โฌ","โธ๏ธ","โน๏ธ","โบ๏ธ","โ๏ธ","๐ŸŽฆ","๐Ÿ”…","๐Ÿ”†","๐Ÿ“ถ","๐Ÿ“ณ","๐Ÿ“ด","โ™€๏ธ","โ™‚๏ธ","โšง๏ธ","โœ–๏ธ","โž•","โž–","โž—","๐ŸŸฐ","โ™พ๏ธ","โ€ผ๏ธ","โ‰๏ธ","โ“","โ”","โ•","โ—","ใ€ฐ๏ธ","๐Ÿ’ฑ","๐Ÿ’ฒ","โš•๏ธ","โ™ป๏ธ","โšœ๏ธ","๐Ÿ”ฑ","๐Ÿ“›","๐Ÿ”ฐ","โญ•","โœ…","โ˜‘๏ธ","โœ”๏ธ","โŒ","โŽ","โžฐ","โžฟ","ใ€ฝ๏ธ","โœณ๏ธ","โœด๏ธ","โ‡๏ธ","ยฉ๏ธ","ยฎ๏ธ","โ„ข๏ธ","#๏ธโƒฃ","*๏ธโƒฃ","0๏ธโƒฃ","1๏ธโƒฃ","2๏ธโƒฃ","3๏ธโƒฃ","4๏ธโƒฃ","5๏ธโƒฃ","6๏ธโƒฃ","7๏ธโƒฃ","8๏ธโƒฃ","9๏ธโƒฃ","๐Ÿ”Ÿ","๐Ÿ” ","๐Ÿ”ก","๐Ÿ”ข","๐Ÿ”ฃ","๐Ÿ”ค","๐Ÿ…ฐ๏ธ","๐Ÿ†Ž","๐Ÿ…ฑ๏ธ","๐Ÿ†‘","๐Ÿ†’","๐Ÿ†“","โ„น๏ธ","๐Ÿ†”","โ“‚๏ธ","๐Ÿ†•","๐Ÿ†–","๐Ÿ…พ๏ธ","๐Ÿ†—","๐Ÿ…ฟ๏ธ","๐Ÿ†˜","๐Ÿ†™","๐Ÿ†š","๐Ÿˆ","๐Ÿˆ‚๏ธ","๐Ÿˆท๏ธ","๐Ÿˆถ","๐Ÿˆฏ","๐Ÿ‰","๐Ÿˆน","๐Ÿˆš","๐Ÿˆฒ","๐Ÿ‰‘","๐Ÿˆธ","๐Ÿˆด","๐Ÿˆณ","ใŠ—๏ธ","ใŠ™๏ธ","๐Ÿˆบ","๐Ÿˆต","๐Ÿ”ด","๐ŸŸ ","๐ŸŸก","๐ŸŸข","๐Ÿ”ต","๐ŸŸฃ","๐ŸŸค","โšซ","โšช","๐ŸŸฅ","๐ŸŸง","๐ŸŸจ","๐ŸŸฉ","๐ŸŸฆ","๐ŸŸช","๐ŸŸซ","โฌ›","โฌœ","โ—ผ๏ธ","โ—ป๏ธ","โ—พ","โ—ฝ","โ–ช๏ธ","โ–ซ๏ธ","๐Ÿ”ถ","๐Ÿ”ท","๐Ÿ”ธ","๐Ÿ”น","๐Ÿ”บ","๐Ÿ”ป","๐Ÿ’ ","๐Ÿ”˜","๐Ÿ”ณ","๐Ÿ”ฒ"],"๐Ÿ":["๐Ÿ","๐Ÿšฉ","๐ŸŽŒ","๐Ÿด","๐Ÿณ๏ธ","๐Ÿณ๏ธโ€๐ŸŒˆ","๐Ÿณ๏ธโ€โšง๏ธ","๐Ÿดโ€โ˜ ๏ธ","๐Ÿ‡ฆ๐Ÿ‡จ","๐Ÿ‡ฆ๐Ÿ‡ฉ","๐Ÿ‡ฆ๐Ÿ‡ช","๐Ÿ‡ฆ๐Ÿ‡ซ","๐Ÿ‡ฆ๐Ÿ‡ฌ","๐Ÿ‡ฆ๐Ÿ‡ฎ","๐Ÿ‡ฆ๐Ÿ‡ฑ","๐Ÿ‡ฆ๐Ÿ‡ฒ","๐Ÿ‡ฆ๐Ÿ‡ด","๐Ÿ‡ฆ๐Ÿ‡ถ","๐Ÿ‡ฆ๐Ÿ‡ท","๐Ÿ‡ฆ๐Ÿ‡ธ","๐Ÿ‡ฆ๐Ÿ‡น","๐Ÿ‡ฆ๐Ÿ‡บ","๐Ÿ‡ฆ๐Ÿ‡ผ","๐Ÿ‡ฆ๐Ÿ‡ฝ","๐Ÿ‡ฆ๐Ÿ‡ฟ","๐Ÿ‡ง๐Ÿ‡ฆ","๐Ÿ‡ง๐Ÿ‡ง","๐Ÿ‡ง๐Ÿ‡ฉ","๐Ÿ‡ง๐Ÿ‡ช","๐Ÿ‡ง๐Ÿ‡ซ","๐Ÿ‡ง๐Ÿ‡ฌ","๐Ÿ‡ง๐Ÿ‡ญ","๐Ÿ‡ง๐Ÿ‡ฎ","๐Ÿ‡ง๐Ÿ‡ฏ","๐Ÿ‡ง๐Ÿ‡ฑ","๐Ÿ‡ง๐Ÿ‡ฒ","๐Ÿ‡ง๐Ÿ‡ณ","๐Ÿ‡ง๐Ÿ‡ด","๐Ÿ‡ง๐Ÿ‡ถ","๐Ÿ‡ง๐Ÿ‡ท","๐Ÿ‡ง๐Ÿ‡ธ","๐Ÿ‡ง๐Ÿ‡น","๐Ÿ‡ง๐Ÿ‡ป","๐Ÿ‡ง๐Ÿ‡ผ","๐Ÿ‡ง๐Ÿ‡พ","๐Ÿ‡ง๐Ÿ‡ฟ","๐Ÿ‡จ๐Ÿ‡ฆ","๐Ÿ‡จ๐Ÿ‡จ","๐Ÿ‡จ๐Ÿ‡ฉ","๐Ÿ‡จ๐Ÿ‡ซ","๐Ÿ‡จ๐Ÿ‡ฌ","๐Ÿ‡จ๐Ÿ‡ญ","๐Ÿ‡จ๐Ÿ‡ฎ","๐Ÿ‡จ๐Ÿ‡ฐ","๐Ÿ‡จ๐Ÿ‡ฑ","๐Ÿ‡จ๐Ÿ‡ฒ","๐Ÿ‡จ๐Ÿ‡ณ","๐Ÿ‡จ๐Ÿ‡ด","๐Ÿ‡จ๐Ÿ‡ต","๐Ÿ‡จ๐Ÿ‡ท","๐Ÿ‡จ๐Ÿ‡บ","๐Ÿ‡จ๐Ÿ‡ป","๐Ÿ‡จ๐Ÿ‡ผ","๐Ÿ‡จ๐Ÿ‡ฝ","๐Ÿ‡จ๐Ÿ‡พ","๐Ÿ‡จ๐Ÿ‡ฟ","๐Ÿ‡ฉ๐Ÿ‡ช","๐Ÿ‡ฉ๐Ÿ‡ฌ","๐Ÿ‡ฉ๐Ÿ‡ฏ","๐Ÿ‡ฉ๐Ÿ‡ฐ","๐Ÿ‡ฉ๐Ÿ‡ฒ","๐Ÿ‡ฉ๐Ÿ‡ด","๐Ÿ‡ฉ๐Ÿ‡ฟ","๐Ÿ‡ช๐Ÿ‡ฆ","๐Ÿ‡ช๐Ÿ‡จ","๐Ÿ‡ช๐Ÿ‡ช","๐Ÿ‡ช๐Ÿ‡ฌ","๐Ÿ‡ช๐Ÿ‡ญ","๐Ÿ‡ช๐Ÿ‡ท","๐Ÿ‡ช๐Ÿ‡ธ","๐Ÿ‡ช๐Ÿ‡น","๐Ÿ‡ช๐Ÿ‡บ","๐Ÿ‡ซ๐Ÿ‡ฎ","๐Ÿ‡ซ๐Ÿ‡ฏ","๐Ÿ‡ซ๐Ÿ‡ฐ","๐Ÿ‡ซ๐Ÿ‡ฒ","๐Ÿ‡ซ๐Ÿ‡ด","๐Ÿ‡ซ๐Ÿ‡ท","๐Ÿ‡ฌ๐Ÿ‡ฆ","๐Ÿ‡ฌ๐Ÿ‡ง","๐Ÿ‡ฌ๐Ÿ‡ฉ","๐Ÿ‡ฌ๐Ÿ‡ช","๐Ÿ‡ฌ๐Ÿ‡ซ","๐Ÿ‡ฌ๐Ÿ‡ฌ","๐Ÿ‡ฌ๐Ÿ‡ญ","๐Ÿ‡ฌ๐Ÿ‡ฎ","๐Ÿ‡ฌ๐Ÿ‡ฑ","๐Ÿ‡ฌ๐Ÿ‡ฒ","๐Ÿ‡ฌ๐Ÿ‡ณ","๐Ÿ‡ฌ๐Ÿ‡ต","๐Ÿ‡ฌ๐Ÿ‡ถ","๐Ÿ‡ฌ๐Ÿ‡ท","๐Ÿ‡ฌ๐Ÿ‡ธ","๐Ÿ‡ฌ๐Ÿ‡น","๐Ÿ‡ฌ๐Ÿ‡บ","๐Ÿ‡ฌ๐Ÿ‡ผ","๐Ÿ‡ฌ๐Ÿ‡พ","๐Ÿ‡ญ๐Ÿ‡ฐ","๐Ÿ‡ญ๐Ÿ‡ฒ","๐Ÿ‡ญ๐Ÿ‡ณ","๐Ÿ‡ญ๐Ÿ‡ท","๐Ÿ‡ญ๐Ÿ‡น","๐Ÿ‡ญ๐Ÿ‡บ","๐Ÿ‡ฎ๐Ÿ‡จ","๐Ÿ‡ฎ๐Ÿ‡ฉ","๐Ÿ‡ฎ๐Ÿ‡ช","๐Ÿ‡ฎ๐Ÿ‡ฑ","๐Ÿ‡ฎ๐Ÿ‡ฒ","๐Ÿ‡ฎ๐Ÿ‡ณ","๐Ÿ‡ฎ๐Ÿ‡ด","๐Ÿ‡ฎ๐Ÿ‡ถ","๐Ÿ‡ฎ๐Ÿ‡ท","๐Ÿ‡ฎ๐Ÿ‡ธ","๐Ÿ‡ฎ๐Ÿ‡น","๐Ÿ‡ฏ๐Ÿ‡ช","๐Ÿ‡ฏ๐Ÿ‡ฒ","๐Ÿ‡ฏ๐Ÿ‡ด","๐Ÿ‡ฏ๐Ÿ‡ต","๐Ÿ‡ฐ๐Ÿ‡ช","๐Ÿ‡ฐ๐Ÿ‡ฌ","๐Ÿ‡ฐ๐Ÿ‡ญ","๐Ÿ‡ฐ๐Ÿ‡ฎ","๐Ÿ‡ฐ๐Ÿ‡ฒ","๐Ÿ‡ฐ๐Ÿ‡ณ","๐Ÿ‡ฐ๐Ÿ‡ต","๐Ÿ‡ฐ๐Ÿ‡ท","๐Ÿ‡ฐ๐Ÿ‡ผ","๐Ÿ‡ฐ๐Ÿ‡พ","๐Ÿ‡ฐ๐Ÿ‡ฟ","๐Ÿ‡ฑ๐Ÿ‡ฆ","๐Ÿ‡ฑ๐Ÿ‡ง","๐Ÿ‡ฑ๐Ÿ‡จ","๐Ÿ‡ฑ๐Ÿ‡ฎ","๐Ÿ‡ฑ๐Ÿ‡ฐ","๐Ÿ‡ฑ๐Ÿ‡ท","๐Ÿ‡ฑ๐Ÿ‡ธ","๐Ÿ‡ฑ๐Ÿ‡น","๐Ÿ‡ฑ๐Ÿ‡บ","๐Ÿ‡ฑ๐Ÿ‡ป","๐Ÿ‡ฑ๐Ÿ‡พ","๐Ÿ‡ฒ๐Ÿ‡ฆ","๐Ÿ‡ฒ๐Ÿ‡จ","๐Ÿ‡ฒ๐Ÿ‡ฉ","๐Ÿ‡ฒ๐Ÿ‡ช","๐Ÿ‡ฒ๐Ÿ‡ซ","๐Ÿ‡ฒ๐Ÿ‡ฌ","๐Ÿ‡ฒ๐Ÿ‡ญ","๐Ÿ‡ฒ๐Ÿ‡ฐ","๐Ÿ‡ฒ๐Ÿ‡ฑ","๐Ÿ‡ฒ๐Ÿ‡ฒ","๐Ÿ‡ฒ๐Ÿ‡ณ","๐Ÿ‡ฒ๐Ÿ‡ด","๐Ÿ‡ฒ๐Ÿ‡ต","๐Ÿ‡ฒ๐Ÿ‡ถ","๐Ÿ‡ฒ๐Ÿ‡ท","๐Ÿ‡ฒ๐Ÿ‡ธ","๐Ÿ‡ฒ๐Ÿ‡น","๐Ÿ‡ฒ๐Ÿ‡บ","๐Ÿ‡ฒ๐Ÿ‡ป","๐Ÿ‡ฒ๐Ÿ‡ผ","๐Ÿ‡ฒ๐Ÿ‡ฝ","๐Ÿ‡ฒ๐Ÿ‡พ","๐Ÿ‡ฒ๐Ÿ‡ฟ","๐Ÿ‡ณ๐Ÿ‡ฆ","๐Ÿ‡ณ๐Ÿ‡จ","๐Ÿ‡ณ๐Ÿ‡ช","๐Ÿ‡ณ๐Ÿ‡ซ","๐Ÿ‡ณ๐Ÿ‡ฌ","๐Ÿ‡ณ๐Ÿ‡ฎ","๐Ÿ‡ณ๐Ÿ‡ฑ","๐Ÿ‡ณ๐Ÿ‡ด","๐Ÿ‡ณ๐Ÿ‡ต","๐Ÿ‡ณ๐Ÿ‡ท","๐Ÿ‡ณ๐Ÿ‡บ","๐Ÿ‡ณ๐Ÿ‡ฟ","๐Ÿ‡ด๐Ÿ‡ฒ","๐Ÿ‡ต๐Ÿ‡ฆ","๐Ÿ‡ต๐Ÿ‡ช","๐Ÿ‡ต๐Ÿ‡ซ","๐Ÿ‡ต๐Ÿ‡ฌ","๐Ÿ‡ต๐Ÿ‡ญ","๐Ÿ‡ต๐Ÿ‡ฐ","๐Ÿ‡ต๐Ÿ‡ฑ","๐Ÿ‡ต๐Ÿ‡ฒ","๐Ÿ‡ต๐Ÿ‡ณ","๐Ÿ‡ต๐Ÿ‡ท","๐Ÿ‡ต๐Ÿ‡ธ","๐Ÿ‡ต๐Ÿ‡น","๐Ÿ‡ต๐Ÿ‡ผ","๐Ÿ‡ต๐Ÿ‡พ","๐Ÿ‡ถ๐Ÿ‡ฆ","๐Ÿ‡ท๐Ÿ‡ช","๐Ÿ‡ท๐Ÿ‡ด","๐Ÿ‡ท๐Ÿ‡ธ","๐Ÿ‡ท๐Ÿ‡บ","๐Ÿ‡ท๐Ÿ‡ผ","๐Ÿ‡ธ๐Ÿ‡ฆ","๐Ÿ‡ธ๐Ÿ‡ง","๐Ÿ‡ธ๐Ÿ‡จ","๐Ÿ‡ธ๐Ÿ‡ฉ","๐Ÿ‡ธ๐Ÿ‡ช","๐Ÿ‡ธ๐Ÿ‡ฌ","๐Ÿ‡ธ๐Ÿ‡ญ","๐Ÿ‡ธ๐Ÿ‡ฎ","๐Ÿ‡ธ๐Ÿ‡ฏ","๐Ÿ‡ธ๐Ÿ‡ฐ","๐Ÿ‡ธ๐Ÿ‡ฑ","๐Ÿ‡ธ๐Ÿ‡ฒ","๐Ÿ‡ธ๐Ÿ‡ณ","๐Ÿ‡ธ๐Ÿ‡ด","๐Ÿ‡ธ๐Ÿ‡ท","๐Ÿ‡ธ๐Ÿ‡ธ","๐Ÿ‡ธ๐Ÿ‡น","๐Ÿ‡ธ๐Ÿ‡ป","๐Ÿ‡ธ๐Ÿ‡ฝ","๐Ÿ‡ธ๐Ÿ‡พ","๐Ÿ‡ธ๐Ÿ‡ฟ","๐Ÿ‡น๐Ÿ‡ฆ","๐Ÿ‡น๐Ÿ‡จ","๐Ÿ‡น๐Ÿ‡ฉ","๐Ÿ‡น๐Ÿ‡ซ","๐Ÿ‡น๐Ÿ‡ฌ","๐Ÿ‡น๐Ÿ‡ญ","๐Ÿ‡น๐Ÿ‡ฏ","๐Ÿ‡น๐Ÿ‡ฐ","๐Ÿ‡น๐Ÿ‡ฑ","๐Ÿ‡น๐Ÿ‡ฒ","๐Ÿ‡น๐Ÿ‡ณ","๐Ÿ‡น๐Ÿ‡ด","๐Ÿ‡น๐Ÿ‡ท","๐Ÿ‡น๐Ÿ‡น","๐Ÿ‡น๐Ÿ‡ป","๐Ÿ‡น๐Ÿ‡ผ","๐Ÿ‡น๐Ÿ‡ฟ","๐Ÿ‡บ๐Ÿ‡ฆ","๐Ÿ‡บ๐Ÿ‡ฌ","๐Ÿ‡บ๐Ÿ‡ฒ","๐Ÿ‡บ๐Ÿ‡ณ","๐Ÿ‡บ๐Ÿ‡ธ","๐Ÿ‡บ๐Ÿ‡พ","๐Ÿ‡บ๐Ÿ‡ฟ","๐Ÿ‡ป๐Ÿ‡ฆ","๐Ÿ‡ป๐Ÿ‡จ","๐Ÿ‡ป๐Ÿ‡ช","๐Ÿ‡ป๐Ÿ‡ฌ","๐Ÿ‡ป๐Ÿ‡ฎ","๐Ÿ‡ป๐Ÿ‡ณ","๐Ÿ‡ป๐Ÿ‡บ","๐Ÿ‡ผ๐Ÿ‡ซ","๐Ÿ‡ผ๐Ÿ‡ธ","๐Ÿ‡ฝ๐Ÿ‡ฐ","๐Ÿ‡พ๐Ÿ‡ช","๐Ÿ‡พ๐Ÿ‡น","๐Ÿ‡ฟ๐Ÿ‡ฆ","๐Ÿ‡ฟ๐Ÿ‡ฒ","๐Ÿ‡ฟ๐Ÿ‡ผ","๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ","๐Ÿด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ","๐Ÿด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ"]}`, + "map_provider": "openstreetmap", + "map_google_tile_type": "regular", + "map_mapbox_ak": "", + "mime_mapping": `{".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",".xltx":"application/vnd.openxmlformats-officedocument.spreadsheetml.template",".potx":"application/vnd.openxmlformats-officedocument.presentationml.template",".ppsx":"application/vnd.openxmlformats-officedocument.presentationml.slideshow",".pptx":"application/vnd.openxmlformats-officedocument.presentationml.presentation",".sldx":"application/vnd.openxmlformats-officedocument.presentationml.slide",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".dotx":"application/vnd.openxmlformats-officedocument.wordprocessingml.template",".xlam":"application/vnd.ms-excel.addin.macroEnabled.12",".xlsb":"application/vnd.ms-excel.sheet.binary.macroEnabled.12",".apk":"application/vnd.android.package-archive",".hqx":"application/mac-binhex40",".cpt":"application/mac-compactpro",".doc":"application/msword",".ogg":"application/ogg",".pdf":"application/pdf",".rtf":"text/rtf",".mif":"application/vnd.mif",".xls":"application/vnd.ms-excel",".ppt":"application/vnd.ms-powerpoint",".odc":"application/vnd.oasis.opendocument.chart",".odb":"application/vnd.oasis.opendocument.database",".odf":"application/vnd.oasis.opendocument.formula",".odg":"application/vnd.oasis.opendocument.graphics",".otg":"application/vnd.oasis.opendocument.graphics-template",".odi":"application/vnd.oasis.opendocument.image",".odp":"application/vnd.oasis.opendocument.presentation",".otp":"application/vnd.oasis.opendocument.presentation-template",".ods":"application/vnd.oasis.opendocument.spreadsheet",".ots":"application/vnd.oasis.opendocument.spreadsheet-template",".odt":"application/vnd.oasis.opendocument.text",".odm":"application/vnd.oasis.opendocument.text-master",".ott":"application/vnd.oasis.opendocument.text-template",".oth":"application/vnd.oasis.opendocument.text-web",".sxw":"application/vnd.sun.xml.writer",".stw":"application/vnd.sun.xml.writer.template",".sxc":"application/vnd.sun.xml.calc",".stc":"application/vnd.sun.xml.calc.template",".sxd":"application/vnd.sun.xml.draw",".std":"application/vnd.sun.xml.draw.template",".sxi":"application/vnd.sun.xml.impress",".sti":"application/vnd.sun.xml.impress.template",".sxg":"application/vnd.sun.xml.writer.global",".sxm":"application/vnd.sun.xml.math",".sis":"application/vnd.symbian.install",".wbxml":"application/vnd.wap.wbxml",".wmlc":"application/vnd.wap.wmlc",".wmlsc":"application/vnd.wap.wmlscriptc",".bcpio":"application/x-bcpio",".torrent":"application/x-bittorrent",".bz2":"application/x-bzip2",".vcd":"application/x-cdlink",".pgn":"application/x-chess-pgn",".cpio":"application/x-cpio",".csh":"application/x-csh",".dvi":"application/x-dvi",".spl":"application/x-futuresplash",".gtar":"application/x-gtar",".hdf":"application/x-hdf",".jar":"application/x-java-archive",".jnlp":"application/x-java-jnlp-file",".js":"application/x-javascript",".ksp":"application/x-kspread",".chrt":"application/x-kchart",".kil":"application/x-killustrator",".latex":"application/x-latex",".rpm":"application/x-rpm",".sh":"application/x-sh",".shar":"application/x-shar",".swf":"application/x-shockwave-flash",".sit":"application/x-stuffit",".sv4cpio":"application/x-sv4cpio",".sv4crc":"application/x-sv4crc",".tar":"application/x-tar",".tcl":"application/x-tcl",".tex":"application/x-tex",".man":"application/x-troff-man",".me":"application/x-troff-me",".ms":"application/x-troff-ms",".ustar":"application/x-ustar",".src":"application/x-wais-source",".zip":"application/zip",".m3u":"audio/x-mpegurl",".ra":"audio/x-pn-realaudio",".wav":"audio/x-wav",".wma":"audio/x-ms-wma",".wax":"audio/x-ms-wax",".pdb":"chemical/x-pdb",".xyz":"chemical/x-xyz",".bmp":"image/bmp",".gif":"image/gif",".ief":"image/ief",".png":"image/png",".wbmp":"image/vnd.wap.wbmp",".ras":"image/x-cmu-raster",".pnm":"image/x-portable-anymap",".pbm":"image/x-portable-bitmap",".pgm":"image/x-portable-graymap",".ppm":"image/x-portable-pixmap",".rgb":"image/x-rgb",".xbm":"image/x-xbitmap",".xpm":"image/x-xpixmap",".xwd":"image/x-xwindowdump",".css":"text/css",".rtx":"text/richtext",".tsv":"text/tab-separated-values",".jad":"text/vnd.sun.j2me.app-descriptor",".wml":"text/vnd.wap.wml",".wmls":"text/vnd.wap.wmlscript",".etx":"text/x-setext",".mxu":"video/vnd.mpegurl",".flv":"video/x-flv",".wm":"video/x-ms-wm",".wmv":"video/x-ms-wmv",".wmx":"video/x-ms-wmx",".wvx":"video/x-ms-wvx",".avi":"video/x-msvideo",".movie":"video/x-sgi-movie",".ice":"x-conference/x-cooltalk",".3gp":"video/3gpp",".ai":"application/postscript",".aif":"audio/x-aiff",".aifc":"audio/x-aiff",".aiff":"audio/x-aiff",".asc":"text/plain",".atom":"application/atom+xml",".au":"audio/basic",".bin":"application/octet-stream",".cdf":"application/x-netcdf",".cgm":"image/cgm",".class":"application/octet-stream",".dcr":"application/x-director",".dif":"video/x-dv",".dir":"application/x-director",".djv":"image/vnd.djvu",".djvu":"image/vnd.djvu",".dll":"application/octet-stream",".dmg":"application/octet-stream",".dms":"application/octet-stream",".dtd":"application/xml-dtd",".dv":"video/x-dv",".dxr":"application/x-director",".eps":"application/postscript",".exe":"application/octet-stream",".ez":"application/andrew-inset",".gram":"application/srgs",".grxml":"application/srgs+xml",".gz":"application/x-gzip",".htm":"text/html",".html":"text/html",".ico":"image/x-icon",".ics":"text/calendar",".ifb":"text/calendar",".iges":"model/iges",".igs":"model/iges",".jp2":"image/jp2",".jpe":"image/jpeg",".jpeg":"image/jpeg",".jpg":"image/jpeg",".kar":"audio/midi",".lha":"application/octet-stream",".lzh":"application/octet-stream",".m4a":"audio/mp4a-latm",".m4p":"audio/mp4a-latm",".m4u":"video/vnd.mpegurl",".m4v":"video/x-m4v",".mac":"image/x-macpaint",".mathml":"application/mathml+xml",".mesh":"model/mesh",".mid":"audio/midi",".midi":"audio/midi",".mov":"video/quicktime",".mp2":"audio/mpeg",".mp3":"audio/mpeg",".mp4":"video/mp4",".mpe":"video/mpeg",".mpeg":"video/mpeg",".mpg":"video/mpeg",".mpga":"audio/mpeg",".msh":"model/mesh",".nc":"application/x-netcdf",".oda":"application/oda",".ogv":"video/ogv",".pct":"image/pict",".pic":"image/pict",".pict":"image/pict",".pnt":"image/x-macpaint",".pntg":"image/x-macpaint",".ps":"application/postscript",".qt":"video/quicktime",".qti":"image/x-quicktime",".qtif":"image/x-quicktime",".ram":"audio/x-pn-realaudio",".rdf":"application/rdf+xml",".rm":"application/vnd.rn-realmedia",".roff":"application/x-troff",".sgm":"text/sgml",".sgml":"text/sgml",".silo":"model/mesh",".skd":"application/x-koan",".skm":"application/x-koan",".skp":"application/x-koan",".skt":"application/x-koan",".smi":"application/smil",".smil":"application/smil",".snd":"audio/basic",".so":"application/octet-stream",".svg":"image/svg+xml",".t":"application/x-troff",".texi":"application/x-texinfo",".texinfo":"application/x-texinfo",".tif":"image/tiff",".tiff":"image/tiff",".tr":"application/x-troff",".txt":"text/plain; charset=utf-8",".vrml":"model/vrml",".vxml":"application/voicexml+xml",".webm":"video/webm",".wrl":"model/vrml",".xht":"application/xhtml+xml",".xhtml":"application/xhtml+xml",".xml":"application/xml",".xsl":"application/xml",".xslt":"application/xslt+xml",".xul":"application/vnd.mozilla.xul+xml",".webp":"image/webp",".323":"text/h323",".aab":"application/x-authoware-bin",".aam":"application/x-authoware-map",".aas":"application/x-authoware-seg",".acx":"application/internet-property-stream",".als":"audio/X-Alpha5",".amc":"application/x-mpeg",".ani":"application/octet-stream",".asd":"application/astound",".asf":"video/x-ms-asf",".asn":"application/astound",".asp":"application/x-asap",".asr":"video/x-ms-asf",".asx":"video/x-ms-asf",".avb":"application/octet-stream",".awb":"audio/amr-wb",".axs":"application/olescript",".bas":"text/plain",".bin ":"application/octet-stream",".bld":"application/bld",".bld2":"application/bld2",".bpk":"application/octet-stream",".c":"text/plain",".cal":"image/x-cals",".cat":"application/vnd.ms-pkiseccat",".ccn":"application/x-cnc",".cco":"application/x-cocoa",".cer":"application/x-x509-ca-cert",".cgi":"magnus-internal/cgi",".chat":"application/x-chat",".clp":"application/x-msclip",".cmx":"image/x-cmx",".co":"application/x-cult3d-object",".cod":"image/cis-cod",".conf":"text/plain",".cpp":"text/plain",".crd":"application/x-mscardfile",".crl":"application/pkix-crl",".crt":"application/x-x509-ca-cert",".csm":"chemical/x-csml",".csml":"chemical/x-csml",".cur":"application/octet-stream",".dcm":"x-lml/x-evm",".dcx":"image/x-dcx",".der":"application/x-x509-ca-cert",".dhtml":"text/html",".dot":"application/msword",".dwf":"drawing/x-dwf",".dwg":"application/x-autocad",".dxf":"application/x-autocad",".ebk":"application/x-expandedbook",".emb":"chemical/x-embl-dl-nucleotide",".embl":"chemical/x-embl-dl-nucleotide",".epub":"application/epub+zip",".eri":"image/x-eri",".es":"audio/echospeech",".esl":"audio/echospeech",".etc":"application/x-earthtime",".evm":"x-lml/x-evm",".evy":"application/envoy",".fh4":"image/x-freehand",".fh5":"image/x-freehand",".fhc":"image/x-freehand",".fif":"application/fractals",".flr":"x-world/x-vrml",".fm":"application/x-maker",".fpx":"image/x-fpx",".fvi":"video/isivideo",".gau":"chemical/x-gaussian-input",".gca":"application/x-gca-compressed",".gdb":"x-lml/x-gdb",".gps":"application/x-gps",".h":"text/plain",".hdm":"text/x-hdml",".hdml":"text/x-hdml",".hlp":"application/winhlp",".hta":"application/hta",".htc":"text/x-component",".hts":"text/html",".htt":"text/webviewhtml",".ifm":"image/gif",".ifs":"image/ifs",".iii":"application/x-iphone",".imy":"audio/melody",".ins":"application/x-internet-signup",".ips":"application/x-ipscript",".ipx":"application/x-ipix",".isp":"application/x-internet-signup",".it":"audio/x-mod",".itz":"audio/x-mod",".ivr":"i-world/i-vrml",".j2k":"image/j2k",".jam":"application/x-jam",".java":"text/plain",".jfif":"image/pipeg",".jpz":"image/jpeg",".jwc":"application/jwc",".kjx":"application/x-kjx",".lak":"x-lml/x-lak",".lcc":"application/fastman",".lcl":"application/x-digitalloca",".lcr":"application/x-digitalloca",".lgh":"application/lgh",".lml":"x-lml/x-lml",".lmlpack":"x-lml/x-lmlpack",".log":"text/plain",".lsf":"video/x-la-asf",".lsx":"video/x-la-asf",".m13":"application/x-msmediaview",".m14":"application/x-msmediaview",".m15":"audio/x-mod",".m3url":"audio/x-mpegurl",".m4b":"audio/mp4a-latm",".ma1":"audio/ma1",".ma2":"audio/ma2",".ma3":"audio/ma3",".ma5":"audio/ma5",".map":"magnus-internal/imagemap",".mbd":"application/mbedlet",".mct":"application/x-mascot",".mdb":"application/x-msaccess",".mdz":"audio/x-mod",".mel":"text/x-vmel",".mht":"message/rfc822",".mhtml":"message/rfc822",".mi":"application/x-mif",".mil":"image/x-cals",".mio":"audio/x-mio",".mmf":"application/x-skt-lbs",".mng":"video/x-mng",".mny":"application/x-msmoney",".moc":"application/x-mocha",".mocha":"application/x-mocha",".mod":"audio/x-mod",".mof":"application/x-yumekara",".mol":"chemical/x-mdl-molfile",".mop":"chemical/x-mopac-input",".mpa":"video/mpeg",".mpc":"application/vnd.mpohun.certificate",".mpg4":"video/mp4",".mpn":"application/vnd.mophun.application",".mpp":"application/vnd.ms-project",".mps":"application/x-mapserver",".mpv2":"video/mpeg",".mrl":"text/x-mrml",".mrm":"application/x-mrm",".msg":"application/vnd.ms-outlook",".mts":"application/metastream",".mtx":"application/metastream",".mtz":"application/metastream",".mvb":"application/x-msmediaview",".mzv":"application/metastream",".nar":"application/zip",".nbmp":"image/nbmp",".ndb":"x-lml/x-ndb",".ndwn":"application/ndwn",".nif":"application/x-nif",".nmz":"application/x-scream",".nokia-op-logo":"image/vnd.nok-oplogo-color",".npx":"application/x-netfpx",".nsnd":"audio/nsnd",".nva":"application/x-neva1",".nws":"message/rfc822",".oom":"application/x-AtlasMate-Plugin",".p10":"application/pkcs10",".p12":"application/x-pkcs12",".p7b":"application/x-pkcs7-certificates",".p7c":"application/x-pkcs7-mime",".p7m":"application/x-pkcs7-mime",".p7r":"application/x-pkcs7-certreqresp",".p7s":"application/x-pkcs7-signature",".pac":"audio/x-pac",".pae":"audio/x-epac",".pan":"application/x-pan",".pcx":"image/x-pcx",".pda":"image/x-pda",".pfr":"application/font-tdpfr",".pfx":"application/x-pkcs12",".pko":"application/ynd.ms-pkipko",".pm":"application/x-perl",".pma":"application/x-perfmon",".pmc":"application/x-perfmon",".pmd":"application/x-pmd",".pml":"application/x-perfmon",".pmr":"application/x-perfmon",".pmw":"application/x-perfmon",".pnz":"image/png",".pot,":"application/vnd.ms-powerpoint",".pps":"application/vnd.ms-powerpoint",".pqf":"application/x-cprplayer",".pqi":"application/cprplayer",".prc":"application/x-prc",".prf":"application/pics-rules",".prop":"text/plain",".proxy":"application/x-ns-proxy-autoconfig",".ptlk":"application/listenup",".pub":"application/x-mspublisher",".pvx":"video/x-pv-pvx",".qcp":"audio/vnd.qcelp",".r3t":"text/vnd.rn-realtext3d",".rar":"application/octet-stream",".rc":"text/plain",".rf":"image/vnd.rn-realflash",".rlf":"application/x-richlink",".rmf":"audio/x-rmf",".rmi":"audio/mid",".rmm":"audio/x-pn-realaudio",".rmvb":"audio/x-pn-realaudio",".rnx":"application/vnd.rn-realplayer",".rp":"image/vnd.rn-realpix",".rt":"text/vnd.rn-realtext",".rte":"x-lml/x-gps",".rtg":"application/metastream",".rv":"video/vnd.rn-realvideo",".rwc":"application/x-rogerwilco",".s3m":"audio/x-mod",".s3z":"audio/x-mod",".sca":"application/x-supercard",".scd":"application/x-msschedule",".sct":"text/scriptlet",".sdf":"application/e-score",".sea":"application/x-stuffit",".setpay":"application/set-payment_old-initiation",".setreg":"application/set-registration-initiation",".shtml":"text/html",".shtm":"text/html",".shw":"application/presentations",".si6":"image/si6",".si7":"image/vnd.stiwap.sis",".si9":"image/vnd.lgtwap.sis",".slc":"application/x-salsa",".smd":"audio/x-smd",".smp":"application/studiom",".smz":"audio/x-smd",".spc":"application/x-pkcs7-certificates",".spr":"application/x-sprite",".sprite":"application/x-sprite",".sdp":"application/sdp",".spt":"application/x-spt",".sst":"application/vnd.ms-pkicertstore",".stk":"application/hyperstudio",".stl":"application/vnd.ms-pkistl",".stm":"text/html",".svf":"image/vnd",".svh":"image/svh",".svr":"x-world/x-svr",".swfl":"application/x-shockwave-flash",".tad":"application/octet-stream",".talk":"text/x-speech",".taz":"application/x-tar",".tbp":"application/x-timbuktu",".tbt":"application/x-timbuktu",".tgz":"application/x-compressed",".thm":"application/vnd.eri.thm",".tki":"application/x-tkined",".tkined":"application/x-tkined",".toc":"application/toc",".toy":"image/toy",".trk":"x-lml/x-gps",".trm":"application/x-msterminal",".tsi":"audio/tsplayer",".tsp":"application/dsptype",".ttf":"application/octet-stream",".ttz":"application/t-time",".uls":"text/iuls",".ult":"audio/x-mod",".uu":"application/x-uuencode",".uue":"application/x-uuencode",".vcf":"text/x-vcard",".vdo":"video/vdo",".vib":"audio/vib",".viv":"video/vivo",".vivo":"video/vivo",".vmd":"application/vocaltec-media-desc",".vmf":"application/vocaltec-media-file",".vmi":"application/x-dreamcast-vms-info",".vms":"application/x-dreamcast-vms",".vox":"audio/voxware",".vqe":"audio/x-twinvq-plugin",".vqf":"audio/x-twinvq",".vql":"audio/x-twinvq",".vre":"x-world/x-vream",".vrt":"x-world/x-vrt",".vrw":"x-world/x-vream",".vts":"workbook/formulaone",".wcm":"application/vnd.ms-works",".wdb":"application/vnd.ms-works",".web":"application/vnd.xara",".wi":"image/wavelet",".wis":"application/x-InstallShield",".wks":"application/vnd.ms-works",".wmd":"application/x-ms-wmd",".wmf":"application/x-msmetafile",".wmlscript":"text/vnd.wap.wmlscript",".wmz":"application/x-ms-wmz",".wpng":"image/x-up-wpng",".wps":"application/vnd.ms-works",".wpt":"x-lml/x-gps",".wri":"application/x-mswrite",".wrz":"x-world/x-vrml",".ws":"text/vnd.wap.wmlscript",".wsc":"application/vnd.wap.wmlscriptc",".wv":"video/wavelet",".wxl":"application/x-wxl",".x-gzip":"application/x-gzip",".xaf":"x-world/x-vrml",".xar":"application/vnd.xara",".xdm":"application/x-xdma",".xdma":"application/x-xdma",".xdw":"application/vnd.fujixerox.docuworks",".xhtm":"application/xhtml+xml",".xla":"application/vnd.ms-excel",".xlc":"application/vnd.ms-excel",".xll":"application/x-excel",".xlm":"application/vnd.ms-excel",".xlt":"application/vnd.ms-excel",".xlw":"application/vnd.ms-excel",".xm":"audio/x-mod",".xmz":"audio/x-mod",".xof":"x-world/x-vrml",".xpi":"application/x-xpinstall",".xsit":"text/xml",".yz1":"application/x-yz1",".z":"application/x-compress",".zac":"application/x-zaurus-zac",".json":"application/json"}`, + "logto_enabled": "0", + "logto_config": `{"direct_sign_in":true,"display_name":"vas.sso"}`, + "qq_login": `0`, + "qq_login_config": `{"direct_sign_in":false}`, + "license": "", + "custom_nav_items": "[]", + "headless_footer_html": "", + "headless_bottom_html": "", + "sidebar_bottom_html": "", + "encrypt_master_key": "", + "encrypt_master_key_vault": "setting", + "encrypt_master_key_file": "", + "show_encryption_status": "1", + "show_desktop_app_promotion": "1", + "fs_event_push_enabled": "1", + "fs_event_push_max_age": "1209600", + "fs_event_push_debounce": "5", + "fts_enabled": "0", + "fts_index_type": "meilisearch", + "fts_extractor_type": "tika", + "fts_meilisearch_endpoint": "", + "fts_meilisearch_api_key": "", + "fts_meilisearch_page_size": "5", + "fts_meilisearch_embed_enabled": "0", + "fts_meilisearch_embed_config": "{}", + "fts_tika_endpoint": "", + "fts_tika_exts": "pdf,doc,docx,xls,xlsx,ppt,pptx,odt,ods,odp,rtf,txt,md,html,htm,epub,csv", + "fts_tika_max_file_size": "26214400", + "fts_chunk_size": "2000", + "viewer_default_apps": "{}", + "expose_user_email": "1", } var RedactedSettings = map[string]struct{}{ diff --git a/inventory/types/types.go b/inventory/types/types.go index 2d8f2f7a..0f5f55fc 100644 --- a/inventory/types/types.go +++ b/inventory/types/types.go @@ -20,13 +20,20 @@ type ( // AutoCompressImages opts this user's uploaded images into the deferred // media post-processing (compression) pipeline (APP-101). Default off. AutoCompressImages bool `json:"auto_compress_images,omitempty"` + // AutoCompressVideos opts this user's uploaded videos into the deferred + // video transcoding pipeline (APP-103). Default off. + AutoCompressVideos bool `json:"auto_compress_videos,omitempty"` } // MediaProcessTaskProps is the JSON payload of a media_process_task row // (APP-101). Signature is the idempotency fingerprint (engine+quality+format // +original size) so a blob is not recompressed with the same parameters. + // For video (APP-103) Engine is "ffmpeg", Codec holds the video codec, Quality + // holds the CRF and Format the target container. New fields are JSON-only + // (props is a JSON column) โ€” no schema migration. MediaProcessTaskProps struct { Engine string `json:"engine,omitempty"` + Codec string `json:"codec,omitempty"` Quality int `json:"quality,omitempty"` Format string `json:"format,omitempty"` OriginalSize int64 `json:"original_size,omitempty"` diff --git a/pkg/filemanager/manager/mediacompress.go b/pkg/filemanager/manager/mediacompress.go index 5d91b557..7f174b13 100644 --- a/pkg/filemanager/manager/mediacompress.go +++ b/pkg/filemanager/manager/mediacompress.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strconv" "strings" @@ -62,54 +63,73 @@ type ( func init() { queue.RegisterResumableTaskFactory(queue.MediaCompressTaskType, NewMediaCompressTaskFromModel) + queue.RegisterResumableTaskFactory(queue.MediaCompressVideoTaskType, NewMediaCompressTaskFromModel) - // Cron: enqueue a bounded batch of pending images onto the dedicated - // MediaProcessQueue. Mirrors pkg/filemanager/manager/recycle.go. + // Cron: enqueue bounded batches of pending media onto their dedicated queues. + // Image and video are independent lanes so either can run with the other off, + // and a long video transcode never blocks image compression (APP-103). Mirrors + // pkg/filemanager/manager/recycle.go. crontab.Register(setting.CronTypeMediaProcess, func(ctx context.Context) { dep := dependency.FromContext(ctx) - l := dep.Logger() + sp := dep.SettingProvider() - mp := dep.SettingProvider().MediaProcess(ctx) - if !mp.ImageEnabled { - return + if mp := sp.MediaProcess(ctx); mp.ImageEnabled { + enqueueMediaProcessLane(ctx, dep, mediaprocesstask.MediaTypeImage, mp.BatchSize, + queue.MediaCompressTaskType, dep.MediaProcessQueue(ctx)) + } + if v := sp.MediaProcessVideo(ctx); v.Enabled { + enqueueMediaProcessLane(ctx, dep, mediaprocesstask.MediaTypeVideo, v.BatchSize, + queue.MediaCompressVideoTaskType, dep.MediaVideoQueue(ctx)) } + }) +} - rows, err := dep.MediaProcessClient().ListPending(ctx, mediaprocesstask.MediaTypeImage, mp.BatchSize) +// enqueueMediaProcessLane lists a bounded batch of pending rows of the given media +// type and enqueues one MediaCompressTask per row onto the provided queue, tagged +// with taskType so it lands on the right (image vs video) lane. +func enqueueMediaProcessLane(ctx context.Context, dep dependency.Dep, mediaType mediaprocesstask.MediaType, batchSize int, taskType string, q queue.Queue) { + l := dep.Logger() + rows, err := dep.MediaProcessClient().ListPending(ctx, mediaType, batchSize) + if err != nil { + l.Error("Failed to list pending media process tasks (%s): %s", mediaType, err) + return + } + if len(rows) == 0 { + return + } + + uc := dep.UserClient() + enqueued := 0 + for _, row := range rows { + // The task must carry an owner (DBTask.Owner() is dereferenced when the + // queue persists the task); attribute it to the blob's owner. + owner, err := uc.GetByID(ctx, row.OwnerID) if err != nil { - l.Error("Failed to list pending media process tasks: %s", err) - return + l.Error("Failed to load owner %d for media compress row %d: %s", row.OwnerID, row.ID, err) + continue } - if len(rows) == 0 { - return + t, err := newMediaCompressTask(ctx, row.ID, owner, taskType) + if err != nil { + l.Error("Failed to create media compress task for row %d: %s", row.ID, err) + continue } - - q := dep.MediaProcessQueue(ctx) - uc := dep.UserClient() - enqueued := 0 - for _, row := range rows { - // The task must carry an owner (DBTask.Owner() is dereferenced when the - // queue persists the task); attribute it to the blob's owner. - owner, err := uc.GetByID(ctx, row.OwnerID) - if err != nil { - l.Error("Failed to load owner %d for media compress row %d: %s", row.OwnerID, row.ID, err) - continue - } - t, err := NewMediaCompressTask(ctx, row.ID, owner) - if err != nil { - l.Error("Failed to create media compress task for row %d: %s", row.ID, err) - continue - } - if err := q.QueueTask(ctx, t); err != nil { - l.Error("Failed to queue media compress task for row %d: %s", row.ID, err) - continue - } - enqueued++ + if err := q.QueueTask(ctx, t); err != nil { + l.Error("Failed to queue media compress task for row %d: %s", row.ID, err) + continue } - l.Info("Enqueued %d media compress task(s) from cron.", enqueued) - }) + enqueued++ + } + l.Info("Enqueued %d media compress task(s) (%s) from cron.", enqueued, mediaType) } func NewMediaCompressTask(ctx context.Context, rowID int, owner *ent.User) (queue.Task, error) { + return newMediaCompressTask(ctx, rowID, owner, queue.MediaCompressTaskType) +} + +// newMediaCompressTask builds a MediaCompressTask tagged with the given queue task +// type, which selects the lane it runs on: MediaCompressTaskType (image) โ†’ +// MediaProcessQueue, MediaCompressVideoTaskType (video) โ†’ MediaVideoQueue. +func newMediaCompressTask(ctx context.Context, rowID int, owner *ent.User, taskType string) (queue.Task, error) { state := &MediaCompressTaskState{RowID: rowID} stateBytes, err := json.Marshal(state) if err != nil { @@ -122,7 +142,7 @@ func NewMediaCompressTask(ctx context.Context, rowID int, owner *ent.User) (queu // persisting the task (pkg/queue/task.go). DirectOwner: owner, Task: &ent.Task{ - Type: queue.MediaCompressTaskType, + Type: taskType, CorrelationID: logging.CorrelationID(ctx), PrivateState: string(stateBytes), PublicState: &types.TaskPublicState{}, @@ -162,8 +182,14 @@ func (m *MediaCompressTask) Do(ctx context.Context) (task.Status, error) { return task.StatusCompleted, nil } - settings := dep.SettingProvider().MediaProcess(ctx) - if !settings.ImageEnabled { + // Master-switch gate per media type: image and video have independent switches + // so one lane can be off while the other runs (APP-103). + sp := dep.SettingProvider() + enabled := sp.MediaProcess(ctx).ImageEnabled + if row.MediaType == mediaprocesstask.MediaTypeVideo { + enabled = sp.MediaProcessVideo(ctx).Enabled + } + if !enabled { _, _ = mpClient.SetStatus(ctx, row.ID, &inventory.MediaProcessStatusArgs{Status: mediaprocesstask.StatusSkipped}) return task.StatusCompleted, nil } @@ -177,7 +203,7 @@ func (m *MediaCompressTask) Do(ctx context.Context) (task.Status, error) { return task.StatusError, fmt.Errorf("failed to mark processing: %w", err) } - if err := m.compress(ctx, dep, settings, row); err != nil { + if err := m.compress(ctx, dep, row); err != nil { // Persist the error; the queue backoff will retry up to MaxRetry, after // which the row stays "processing" โ€” a follow-up sweep can requeue it. _, _ = mpClient.SetStatus(ctx, row.ID, &inventory.MediaProcessStatusArgs{ @@ -190,10 +216,14 @@ func (m *MediaCompressTask) Do(ctx context.Context) (task.Status, error) { return task.StatusCompleted, nil } -// compress runs the full pipeline for one row: read โ†’ compress โ†’ write-back as a -// new version โ†’ mark done. Returns nil on success or a documented skip. -func (m *MediaCompressTask) compress(ctx context.Context, dep dependency.Dep, settings *setting.MediaProcessSetting, row *ent.MediaProcessTask) error { +// compress runs the full pipeline for one row: read โ†’ compress/transcode โ†’ +// write-back as a new version โ†’ mark done. The read, size/idempotency guards, +// write-back and quota accounting are shared between image and video; only the +// engine step branches by media_type. Returns nil on success or a documented skip. +func (m *MediaCompressTask) compress(ctx context.Context, dep dependency.Dep, row *ent.MediaProcessTask) error { mpClient := dep.MediaProcessClient() + sp := dep.SettingProvider() + isVideo := row.MediaType == mediaprocesstask.MediaTypeVideo owner, err := dep.UserClient().GetByID(ctx, row.OwnerID) if err != nil { @@ -212,8 +242,12 @@ func (m *MediaCompressTask) compress(ctx context.Context, dep dependency.Dep, se } defer es.Close() + minSize := sp.MediaProcess(ctx).MinSize + if isVideo { + minSize = sp.MediaProcessVideo(ctx).MinSize + } originalSize := es.Entity().Size() - if originalSize < settings.MinSize { + if originalSize < minSize { _, _ = mpClient.SetStatus(ctx, row.ID, &inventory.MediaProcessStatusArgs{Status: mediaprocesstask.StatusSkipped}) return nil } @@ -227,15 +261,37 @@ func (m *MediaCompressTask) compress(ctx context.Context, dep dependency.Dep, se } sourceExt := strings.ToLower(strings.TrimPrefix(filepath.Ext(file.Name()), ".")) - // Idempotency signature: engine + quality + format + original size. - signature := fmt.Sprintf("%s|q%d|%s|%d", settings.Engine, settings.Quality, settings.Format, originalSize) + // Idempotency signature + the props recorded on completion. Both branch by + // media type: image encodes engine+quality+format, video encodes the ffmpeg + // transcode parameters. A matching signature means the same parameters already + // ran against the same original bytes โ†’ skip. + var ( + signature string + props *types.MediaProcessTaskProps + ) + if isVideo { + v := sp.MediaProcessVideo(ctx) + signature = fmt.Sprintf("ffmpeg|%s|crf%d|%s|%s|%s|%s|%d", + v.Codec, v.CRF, v.Preset, v.Container, v.MaxResolution, v.AudioCodec, originalSize) + props = &types.MediaProcessTaskProps{ + Engine: "ffmpeg", Codec: v.Codec, Quality: v.CRF, Format: v.Container, + OriginalSize: originalSize, Signature: signature, + } + } else { + mp := sp.MediaProcess(ctx) + signature = fmt.Sprintf("%s|q%d|%s|%d", mp.Engine, mp.Quality, mp.Format, originalSize) + props = &types.MediaProcessTaskProps{ + Engine: mp.Engine, Quality: mp.Quality, Format: mp.Format, + OriginalSize: originalSize, Signature: signature, + } + } if row.Props != nil && row.Props.Signature == signature { _, _ = mpClient.SetStatus(ctx, row.ID, &inventory.MediaProcessStatusArgs{Status: mediaprocesstask.StatusSkipped}) return nil } // Materialize a local input path (download if the blob is remote/encrypted). - tempDir := filepath.Join(util.DataPath(dep.SettingProvider().TempPath(ctx)), mediaCompressTempFolder) + tempDir := filepath.Join(util.DataPath(sp.TempPath(ctx)), mediaCompressTempFolder) if err := util.CreatNestedFolder(tempDir); err != nil { return fmt.Errorf("failed to create temp folder: %w", err) } @@ -245,11 +301,19 @@ func (m *MediaCompressTask) compress(ctx context.Context, dep dependency.Dep, se } defer cleanupInput() - // Compress. - outputPath, targetExt, err := compressImage(ctx, settings, dep.SettingProvider(), inputPath, sourceExt, tempDir) + // Compress/transcode. + var ( + outputPath string + targetExt string + ) + if isVideo { + outputPath, targetExt, err = compressVideo(ctx, sp.MediaProcessVideo(ctx), sp, inputPath, sourceExt, tempDir) + } else { + outputPath, targetExt, err = compressImage(ctx, sp.MediaProcess(ctx), sp, inputPath, sourceExt, tempDir) + } if err != nil { if err == errUnsupportedFormat { - m.l.Info("Unsupported image format %q for row %d, skipping.", sourceExt, row.ID) + m.l.Info("Unsupported %s format %q for row %d, skipping.", row.MediaType, sourceExt, row.ID) _, _ = mpClient.SetStatus(ctx, row.ID, &inventory.MediaProcessStatusArgs{Status: mediaprocesstask.StatusSkipped}) return nil } @@ -264,16 +328,14 @@ func (m *MediaCompressTask) compress(ctx context.Context, dep dependency.Dep, se resultSize := outInfo.Size() // No gain (or larger): keep the original, mark done with the signature so it - // is not retried with the same parameters. + // is not retried with the same parameters. This guard is more often hit for + // video (an already well-encoded clip may not shrink on re-encode). if resultSize == 0 || resultSize >= originalSize { m.l.Info("Compression yielded no gain for row %d (%d -> %d), keeping original.", row.ID, originalSize, resultSize) _, _ = mpClient.SetStatus(ctx, row.ID, &inventory.MediaProcessStatusArgs{ Status: mediaprocesstask.StatusDone, ResultSize: originalSize, - Props: &types.MediaProcessTaskProps{ - Engine: settings.Engine, Quality: settings.Quality, Format: settings.Format, - OriginalSize: originalSize, Signature: signature, - }, + Props: props, }) return nil } @@ -305,16 +367,13 @@ func (m *MediaCompressTask) compress(ctx context.Context, dep dependency.Dep, se _, err = mpClient.SetStatus(ctx, row.ID, &inventory.MediaProcessStatusArgs{ Status: mediaprocesstask.StatusDone, ResultSize: resultSize, - Props: &types.MediaProcessTaskProps{ - Engine: settings.Engine, Quality: settings.Quality, Format: settings.Format, - OriginalSize: originalSize, Signature: signature, - }, + Props: props, }) if err != nil { return fmt.Errorf("failed to mark done: %w", err) } - m.l.Info("Compressed row %d: %s %d -> %d bytes (target .%s).", row.ID, sourceExt, originalSize, resultSize, targetExt) + m.l.Info("Compressed row %d (%s): %s %d -> %d bytes (target .%s).", row.ID, row.MediaType, sourceExt, originalSize, resultSize, targetExt) return nil } @@ -444,6 +503,101 @@ func ffmpegCompressArgs(inputPath, outputPath, targetExt string, quality int) ([ } } +// compressVideo transcodes a local video with ffmpeg (the only video engine; vips +// does not process video) and returns the output path + its extension. The target +// container defaults to "keep" (same extension as the source, so the file is not +// renamed). CPU is bounded by -threads and, when enabled, a low process priority +// via nice (best effort, non-Windows only). +func compressVideo(ctx context.Context, v *setting.MediaProcessVideoSetting, settings setting.Provider, inputPath, sourceExt, tempDir string) (string, string, error) { + targetExt := normalizeVideoContainer(v.Container, sourceExt) + if targetExt == "" { + return "", "", errUnsupportedFormat + } + + outputPath := filepath.Join(tempDir, fmt.Sprintf("out_%s.%s", uuid.Must(uuid.NewV4()).String(), targetExt)) + + codec := strings.TrimSpace(v.Codec) + if codec == "" { + codec = "libx264" + } + args := []string{"-y", "-i", inputPath, "-c:v", codec, "-crf", strconv.Itoa(v.CRF)} + if preset := strings.TrimSpace(v.Preset); preset != "" { + args = append(args, "-preset", preset) + } + if scale := videoScaleFilter(v.MaxResolution); scale != "" { + args = append(args, "-vf", scale) + } + if ac := strings.TrimSpace(v.AudioCodec); ac != "" { + args = append(args, "-c:a", ac) + if ab := strings.TrimSpace(v.AudioBitrate); ab != "" { + args = append(args, "-b:a", ab) + } + } + if v.Threads > 0 { + args = append(args, "-threads", strconv.Itoa(v.Threads)) + } + if extra := strings.TrimSpace(v.ExtraArgs); extra != "" { + args = append(args, strings.Fields(extra)...) + } + args = append(args, outputPath) + + bin := settings.FFMpegPath(ctx) + // Yield CPU to real traffic by running the encode at low priority. `nice` is a + // POSIX tool; skip it on Windows where it is unavailable. + if v.Nice && runtime.GOOS != "windows" { + args = append([]string{"-n", "10", bin}, args...) + bin = "nice" + } + + cmd := exec.CommandContext(ctx, bin, args...) + var stdErr bytes.Buffer + cmd.Stderr = &stdErr + if err := cmd.Run(); err != nil { + os.Remove(outputPath) + return "", "", fmt.Errorf("failed to invoke %s: %w, output: %s", bin, err, stdErr.String()) + } + + return outputPath, targetExt, nil +} + +// normalizeVideoContainer resolves the output container/extension. "keep" (default) +// preserves the source extension โ€” but only for recognised video containers, so a +// non-video file yields "" and is skipped. An explicit container overrides it (the +// file is not renamed, matching the image caveat: the bytes change container but +// the name/extension stays โ€” hence "keep" is the recommended default). +func normalizeVideoContainer(container, sourceExt string) string { + c := strings.ToLower(strings.TrimSpace(container)) + if c == "" || c == "keep" { + c = strings.ToLower(strings.TrimSpace(sourceExt)) + } + switch c { + case "mp4", "m4v", "mov", "mkv", "webm", "avi", "flv", "wmv", "mpeg", "mpg", "ts", "m2ts", "3gp", "ogv": + return c + default: + return "" + } +} + +// videoScaleFilter builds an ffmpeg -vf scale filter that caps the output at the +// given WxH without upscaling and keeping the aspect ratio, forcing even +// dimensions (required by yuv420p). An empty or unparseable spec disables scaling. +func videoScaleFilter(maxResolution string) string { + spec := strings.TrimSpace(maxResolution) + if spec == "" { + return "" + } + parts := strings.SplitN(strings.ToLower(spec), "x", 2) + if len(parts) != 2 { + return "" + } + w, errW := strconv.Atoi(strings.TrimSpace(parts[0])) + h, errH := strconv.Atoi(strings.TrimSpace(parts[1])) + if errW != nil || errH != nil || w <= 0 || h <= 0 { + return "" + } + return fmt.Sprintf("scale='min(%d,iw)':'min(%d,ih)':force_original_aspect_ratio=decrease:force_divisible_by=2", w, h) +} + // entitySourceReader is the subset of entitysource.EntitySource the compression // pipeline consumes (kept local to avoid widening the import surface). type entitySourceReader interface { @@ -466,11 +620,6 @@ func (m *manager) enqueueMediaProcessIfEligible(ctx context.Context, session *fs return } - mp := m.settings.MediaProcess(ctx) - if !mp.ImageEnabled || !m.user.Settings.AutoCompressImages { - return - } - mimeType := "" if session != nil && session.Props != nil { mimeType = session.Props.MimeType @@ -478,12 +627,33 @@ func (m *manager) enqueueMediaProcessIfEligible(ctx context.Context, session *fs if mimeType == "" { mimeType = m.dep.MimeDetector(ctx).TypeByName(file.Name()) } - if !strings.HasPrefix(strings.ToLower(mimeType), "image/") { + mimeType = strings.ToLower(mimeType) + + // Route by mime to the matching media type; each has its own master switch, + // per-user opt-in and min-size gate (APP-101 image, APP-103 video). + var ( + mediaType mediaprocesstask.MediaType + minSize int64 + ) + switch { + case strings.HasPrefix(mimeType, "image/"): + mp := m.settings.MediaProcess(ctx) + if !mp.ImageEnabled || !m.user.Settings.AutoCompressImages { + return + } + mediaType, minSize = mediaprocesstask.MediaTypeImage, mp.MinSize + case strings.HasPrefix(mimeType, "video/"): + v := m.settings.MediaProcessVideo(ctx) + if !v.Enabled || !m.user.Settings.AutoCompressVideos { + return + } + mediaType, minSize = mediaprocesstask.MediaTypeVideo, v.MinSize + default: return } entity := file.PrimaryEntity() - if entity == nil || entity.Size() < mp.MinSize { + if entity == nil || entity.Size() < minSize { return } @@ -499,7 +669,7 @@ func (m *manager) enqueueMediaProcessIfEligible(ctx context.Context, session *fs EntityID: entity.ID(), FileID: file.ID(), OwnerID: m.user.ID, - MediaType: mediaprocesstask.MediaTypeImage, + MediaType: mediaType, }); err != nil { m.l.Warning("media process: failed to enqueue pending row for entity %d: %s", entity.ID(), err) } diff --git a/pkg/filemanager/manager/mediacompress_video_test.go b/pkg/filemanager/manager/mediacompress_video_test.go new file mode 100644 index 00000000..9f3b7252 --- /dev/null +++ b/pkg/filemanager/manager/mediacompress_video_test.go @@ -0,0 +1,49 @@ +package manager + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestNormalizeVideoContainer covers APP-103: "keep" preserves the source +// extension for recognised video containers, an explicit container overrides it, +// and non-video / unknown inputs yield "" (โ†’ skipped). +func TestNormalizeVideoContainer(t *testing.T) { + cases := []struct { + container string + sourceExt string + want string + }{ + {"keep", "mp4", "mp4"}, + {"keep", "MOV", "mov"}, // case-insensitive + {"", "mkv", "mkv"}, // empty behaves like keep + {"keep", "txt", ""}, // non-video source โ†’ skip + {"keep", "", ""}, // no extension โ†’ skip + {"mp4", "avi", "mp4"}, // explicit container overrides source + {"webm", "mp4", "webm"}, // explicit container overrides source + {"exe", "mp4", ""}, // unknown explicit container โ†’ skip + } + for _, c := range cases { + assert.Equal(t, c.want, normalizeVideoContainer(c.container, c.sourceExt), + "normalizeVideoContainer(%q, %q)", c.container, c.sourceExt) + } +} + +// TestVideoScaleFilter covers APP-103: a valid WxH yields a scale filter that caps +// without upscaling (min(...)) and forces even dimensions; empty/invalid disables +// scaling (empty string โ†’ no -vf). +func TestVideoScaleFilter(t *testing.T) { + assert.Equal(t, "", videoScaleFilter(""), "empty spec disables scaling") + assert.Equal(t, "", videoScaleFilter("1080"), "missing dimension is invalid") + assert.Equal(t, "", videoScaleFilter("axb"), "non-numeric is invalid") + assert.Equal(t, "", videoScaleFilter("0x0"), "non-positive is invalid") + + f := videoScaleFilter("1920x1080") + assert.Contains(t, f, "min(1920,iw)") + assert.Contains(t, f, "min(1080,ih)") + assert.Contains(t, f, "force_original_aspect_ratio=decrease") + assert.Contains(t, f, "force_divisible_by=2") + assert.True(t, strings.HasPrefix(f, "scale=")) +} diff --git a/pkg/queue/task.go b/pkg/queue/task.go index f662525b..17113994 100644 --- a/pkg/queue/task.go +++ b/pkg/queue/task.go @@ -105,6 +105,7 @@ const ( RemoteDownloadTaskType = "remote_download" ImportTaskType = "import" MediaCompressTaskType = "media_compress" + MediaCompressVideoTaskType = "media_compress_video" MediaBackfillTaskType = "media_backfill" FullTextIndexTaskType = "full_text_index" diff --git a/pkg/setting/provider.go b/pkg/setting/provider.go index 46121f08..84e058c1 100644 --- a/pkg/setting/provider.go +++ b/pkg/setting/provider.go @@ -158,6 +158,8 @@ type ( Cron(ctx context.Context, t CronType) string // MediaProcess returns the media post-processing (image compression) settings. MediaProcess(ctx context.Context) *MediaProcessSetting + // MediaProcessVideo returns the deferred video transcoding settings (APP-103). + MediaProcessVideo(ctx context.Context) *MediaProcessVideoSetting // Theme returns the theme settings. Theme(ctx context.Context) *Theme // Logo returns the logo settings. @@ -459,6 +461,33 @@ func (s *settingProvider) MediaProcess(ctx context.Context) *MediaProcessSetting } } +func (s *settingProvider) MediaProcessVideo(ctx context.Context) *MediaProcessVideoSetting { + workerNum := s.getInt(ctx, "media_compress_video_worker_num", 1) + if workerNum <= 0 { + workerNum = 1 + } + threads := s.getInt(ctx, "media_compress_video_threads", 1) + if threads <= 0 { + threads = 1 + } + return &MediaProcessVideoSetting{ + Enabled: s.getBoolean(ctx, "media_compress_video_enabled", false), + Codec: s.getString(ctx, "media_compress_video_codec", "libx264"), + CRF: s.getInt(ctx, "media_compress_video_crf", 28), + Preset: s.getString(ctx, "media_compress_video_preset", "medium"), + Container: s.getString(ctx, "media_compress_video_container", "keep"), + MaxResolution: s.getString(ctx, "media_compress_video_max_resolution", "1920x1080"), + AudioCodec: s.getString(ctx, "media_compress_video_audio_codec", "aac"), + AudioBitrate: s.getString(ctx, "media_compress_video_audio_bitrate", "128k"), + ExtraArgs: s.getString(ctx, "media_compress_video_args", ""), + WorkerNum: workerNum, + BatchSize: s.getInt(ctx, "media_compress_video_batch_size", 10), + Threads: threads, + Nice: s.getBoolean(ctx, "media_compress_video_nice", true), + MinSize: s.getInt64(ctx, "media_compress_video_min_size", 10485760), + } +} + func (s *settingProvider) BuiltinThumbGeneratorEnabled(ctx context.Context) bool { return s.getBoolean(ctx, "thumb_builtin_enabled", true) } diff --git a/pkg/setting/types.go b/pkg/setting/types.go index cdc290ac..e0279e50 100644 --- a/pkg/setting/types.go +++ b/pkg/setting/types.go @@ -103,6 +103,27 @@ type ( ResultMode string // "version" | "replace" | "auto" MinSize int64 // skip blobs smaller than this (bytes) } + + // MediaProcessVideoSetting carries the deferred video transcoding parameters + // (APP-103), all editable from the admin panel. The engine is always ffmpeg + // (vips does not process video). Video runs on its own dedicated queue so a + // long transcode never blocks image compression. + MediaProcessVideoSetting struct { + Enabled bool // master switch for video transcoding + Codec string // video codec, e.g. "libx264" | "libx265" + CRF int // constant rate factor (lower = better quality/bigger) + Preset string // ffmpeg preset (speed/size trade-off), e.g. "medium" + Container string // "keep" | "mp4" | "webm" โ€” output container/extension + MaxResolution string // downscale cap, e.g. "1920x1080" (empty = no cap) + AudioCodec string // audio codec, e.g. "aac" + AudioBitrate string // audio bitrate, e.g. "128k" + ExtraArgs string // extra ffmpeg flags + WorkerNum int // transcoding concurrency (dedicated key; default 1) + BatchSize int // max rows the cron enqueues per run + Threads int // ffmpeg -threads per encode (approximate CPU cap) + Nice bool // run ffmpeg at low priority (best effort, non-Windows) + MinSize int64 // skip blobs smaller than this (bytes) + } ) type ThumbEncode struct { @@ -118,6 +139,10 @@ var ( QueueTypeSlave = QueueType("slave") QueueTypeRemoteDownload = QueueType("remote_download") QueueTypeMediaProcess = QueueType("media_process") + // QueueTypeMediaProcessVideo is the dedicated queue for video transcoding + // (APP-103), kept separate from media_process so a long transcode does not + // block image compression. + QueueTypeMediaProcessVideo = QueueType("media_process_video") ) type CronType string diff --git a/service/admin/site.go b/service/admin/site.go index 7dedfc72..1c93efa9 100644 --- a/service/admin/site.go +++ b/service/admin/site.go @@ -7,6 +7,8 @@ import ( "fmt" "net/url" "reflect" + "runtime" + "strconv" "strings" "time" @@ -190,9 +192,17 @@ type ( GetSettingParamCtx struct{} ) +// cpuNumSettingKey is a virtual (non-DB) key: when requested, GetSetting returns +// the CPU count visible to the process (runtime.NumCPU()) so the admin UI can cap +// the "threads per compression" selector to the available cores (APP-103). +const cpuNumSettingKey = "cpu_num" + func (s *GetSettingService) GetSetting(c *gin.Context) (map[string]string, error) { dep := dependency.FromContext(c) res, err := dep.SettingClient().Gets(c, lo.Filter(s.Keys, func(item string, index int) bool { + if strings.ToLower(item) == cpuNumSettingKey { + return false + } _, ok := inventory.RedactedSettings[strings.ToLower(item)] return !ok })) @@ -200,6 +210,10 @@ func (s *GetSettingService) GetSetting(c *gin.Context) (map[string]string, error return nil, serializer.NewError(serializer.CodeDBError, "Failed to get settings", err) } + if lo.ContainsBy(s.Keys, func(item string) bool { return strings.ToLower(item) == cpuNumSettingKey }) { + res[cpuNumSettingKey] = strconv.Itoa(runtime.NumCPU()) + } + return res, nil } @@ -219,64 +233,70 @@ var ( "secret_key": secretKeyPreProcessor, } postprocessors = map[string]SettingPostProcessor{ - "mime_mapping": mimeMappingPostProcessor, - "media_meta_exif": mediaMetaPostProcessor, - "media_meta_music": mediaMetaPostProcessor, - "media_meta_ffprobe": mediaMetaPostProcessor, - "smtpUser": emailPostProcessor, - "smtpPass": emailPostProcessor, - "smtpHost": emailPostProcessor, - "smtpPort": emailPostProcessor, - "smtpEncryption": emailPostProcessor, - "smtpFrom": emailPostProcessor, - "replyTo": emailPostProcessor, - "fromName": emailPostProcessor, - "fromAdress": emailPostProcessor, - "queue_media_meta_worker_num": mediaMetaQueuePostProcessor, - "queue_media_meta_max_execution": mediaMetaQueuePostProcessor, - "queue_media_meta_backoff_factor": mediaMetaQueuePostProcessor, - "queue_media_meta_backoff_max_duration": mediaMetaQueuePostProcessor, - "queue_media_meta_max_retry": mediaMetaQueuePostProcessor, - "queue_media_meta_retry_delay": mediaMetaQueuePostProcessor, - "queue_thumb_worker_num": thumbQueuePostProcessor, - "queue_thumb_max_execution": thumbQueuePostProcessor, - "queue_thumb_backoff_factor": thumbQueuePostProcessor, - "queue_thumb_backoff_max_duration": thumbQueuePostProcessor, - "queue_thumb_max_retry": thumbQueuePostProcessor, - "queue_thumb_retry_delay": thumbQueuePostProcessor, - "queue_recycle_worker_num": entityRecycleQueuePostProcessor, - "queue_recycle_max_execution": entityRecycleQueuePostProcessor, - "queue_recycle_backoff_factor": entityRecycleQueuePostProcessor, - "queue_recycle_backoff_max_duration": entityRecycleQueuePostProcessor, - "queue_recycle_max_retry": entityRecycleQueuePostProcessor, - "queue_recycle_retry_delay": entityRecycleQueuePostProcessor, - "queue_io_intense_worker_num": ioIntenseQueuePostProcessor, - "queue_io_intense_max_execution": ioIntenseQueuePostProcessor, - "queue_io_intense_backoff_factor": ioIntenseQueuePostProcessor, - "queue_io_intense_backoff_max_duration": ioIntenseQueuePostProcessor, - "queue_io_intense_max_retry": ioIntenseQueuePostProcessor, - "queue_io_intense_retry_delay": ioIntenseQueuePostProcessor, - "queue_remote_download_worker_num": remoteDownloadQueuePostProcessor, - "queue_remote_download_max_execution": remoteDownloadQueuePostProcessor, - "queue_remote_download_backoff_factor": remoteDownloadQueuePostProcessor, - "queue_remote_download_backoff_max_duration": remoteDownloadQueuePostProcessor, - "queue_remote_download_max_retry": remoteDownloadQueuePostProcessor, - "queue_remote_download_retry_delay": remoteDownloadQueuePostProcessor, - "media_compress_worker_num": mediaProcessQueuePostProcessor, - "queue_media_process_max_execution": mediaProcessQueuePostProcessor, - "queue_media_process_backoff_factor": mediaProcessQueuePostProcessor, - "queue_media_process_backoff_max_duration": mediaProcessQueuePostProcessor, - "queue_media_process_max_retry": mediaProcessQueuePostProcessor, - "queue_media_process_retry_delay": mediaProcessQueuePostProcessor, - "secret_key": secretKeyPostProcessor, - "fts_meilisearch_embed_config": meilisearchPostProcessor, - "fts_meilisearch_endpoint": meilisearchPostProcessor, - "fts_meilisearch_api_key": meilisearchPostProcessor, - "fts_meilisearch_embed_enabled": meilisearchPostProcessor, - "fts_meilisearch_page_size": meilisearchPostProcessor, - "fts_tika_endpoint": tikaPostProcessor, - "fts_tika_exts": tikaPostProcessor, - "fts_tika_max_file_size": tikaPostProcessor, + "mime_mapping": mimeMappingPostProcessor, + "media_meta_exif": mediaMetaPostProcessor, + "media_meta_music": mediaMetaPostProcessor, + "media_meta_ffprobe": mediaMetaPostProcessor, + "smtpUser": emailPostProcessor, + "smtpPass": emailPostProcessor, + "smtpHost": emailPostProcessor, + "smtpPort": emailPostProcessor, + "smtpEncryption": emailPostProcessor, + "smtpFrom": emailPostProcessor, + "replyTo": emailPostProcessor, + "fromName": emailPostProcessor, + "fromAdress": emailPostProcessor, + "queue_media_meta_worker_num": mediaMetaQueuePostProcessor, + "queue_media_meta_max_execution": mediaMetaQueuePostProcessor, + "queue_media_meta_backoff_factor": mediaMetaQueuePostProcessor, + "queue_media_meta_backoff_max_duration": mediaMetaQueuePostProcessor, + "queue_media_meta_max_retry": mediaMetaQueuePostProcessor, + "queue_media_meta_retry_delay": mediaMetaQueuePostProcessor, + "queue_thumb_worker_num": thumbQueuePostProcessor, + "queue_thumb_max_execution": thumbQueuePostProcessor, + "queue_thumb_backoff_factor": thumbQueuePostProcessor, + "queue_thumb_backoff_max_duration": thumbQueuePostProcessor, + "queue_thumb_max_retry": thumbQueuePostProcessor, + "queue_thumb_retry_delay": thumbQueuePostProcessor, + "queue_recycle_worker_num": entityRecycleQueuePostProcessor, + "queue_recycle_max_execution": entityRecycleQueuePostProcessor, + "queue_recycle_backoff_factor": entityRecycleQueuePostProcessor, + "queue_recycle_backoff_max_duration": entityRecycleQueuePostProcessor, + "queue_recycle_max_retry": entityRecycleQueuePostProcessor, + "queue_recycle_retry_delay": entityRecycleQueuePostProcessor, + "queue_io_intense_worker_num": ioIntenseQueuePostProcessor, + "queue_io_intense_max_execution": ioIntenseQueuePostProcessor, + "queue_io_intense_backoff_factor": ioIntenseQueuePostProcessor, + "queue_io_intense_backoff_max_duration": ioIntenseQueuePostProcessor, + "queue_io_intense_max_retry": ioIntenseQueuePostProcessor, + "queue_io_intense_retry_delay": ioIntenseQueuePostProcessor, + "queue_remote_download_worker_num": remoteDownloadQueuePostProcessor, + "queue_remote_download_max_execution": remoteDownloadQueuePostProcessor, + "queue_remote_download_backoff_factor": remoteDownloadQueuePostProcessor, + "queue_remote_download_backoff_max_duration": remoteDownloadQueuePostProcessor, + "queue_remote_download_max_retry": remoteDownloadQueuePostProcessor, + "queue_remote_download_retry_delay": remoteDownloadQueuePostProcessor, + "media_compress_worker_num": mediaProcessQueuePostProcessor, + "queue_media_process_max_execution": mediaProcessQueuePostProcessor, + "queue_media_process_backoff_factor": mediaProcessQueuePostProcessor, + "queue_media_process_backoff_max_duration": mediaProcessQueuePostProcessor, + "queue_media_process_max_retry": mediaProcessQueuePostProcessor, + "queue_media_process_retry_delay": mediaProcessQueuePostProcessor, + "media_compress_video_worker_num": mediaVideoQueuePostProcessor, + "queue_media_process_video_max_execution": mediaVideoQueuePostProcessor, + "queue_media_process_video_backoff_factor": mediaVideoQueuePostProcessor, + "queue_media_process_video_backoff_max_duration": mediaVideoQueuePostProcessor, + "queue_media_process_video_max_retry": mediaVideoQueuePostProcessor, + "queue_media_process_video_retry_delay": mediaVideoQueuePostProcessor, + "secret_key": secretKeyPostProcessor, + "fts_meilisearch_embed_config": meilisearchPostProcessor, + "fts_meilisearch_endpoint": meilisearchPostProcessor, + "fts_meilisearch_api_key": meilisearchPostProcessor, + "fts_meilisearch_embed_enabled": meilisearchPostProcessor, + "fts_meilisearch_page_size": meilisearchPostProcessor, + "fts_tika_endpoint": tikaPostProcessor, + "fts_tika_exts": tikaPostProcessor, + "fts_tika_max_file_size": tikaPostProcessor, } ) @@ -425,6 +445,12 @@ func mediaProcessQueuePostProcessor(ctx context.Context, settings map[string]str return nil } +func mediaVideoQueuePostProcessor(ctx context.Context, settings map[string]string) error { + dep := dependency.FromContext(ctx) + dep.MediaVideoQueue(context.WithValue(ctx, dependency.ReloadCtx{}, true)).Start() + return nil +} + func secretKeyPostProcessor(ctx context.Context, settings map[string]string) error { dep := dependency.FromContext(ctx) dep.KV().Delete(manager.EntityUrlCacheKeyPrefix) diff --git a/service/admin/task.go b/service/admin/task.go index c77cf50c..856e6d9c 100644 --- a/service/admin/task.go +++ b/service/admin/task.go @@ -28,6 +28,7 @@ func GetQueueMetrics(c *gin.Context) ([]QueueMetric, error) { remoteDownload := dep.RemoteDownloadQueue(c) thumb := dep.ThumbQueue(c) mediaProcess := dep.MediaProcessQueue(c) + mediaVideo := dep.MediaVideoQueue(c) res = append(res, QueueMetric{ Name: setting.QueueTypeMediaMeta, @@ -77,6 +78,14 @@ func GetQueueMetrics(c *gin.Context) ([]QueueMetric, error) { SubmittedTasks: mediaProcess.SubmittedTasks(), SuspendingTasks: mediaProcess.SuspendingTasks(), }) + res = append(res, QueueMetric{ + Name: setting.QueueTypeMediaProcessVideo, + BusyWorkers: mediaVideo.BusyWorkers(), + SuccessTasks: mediaVideo.SuccessTasks(), + FailureTasks: mediaVideo.FailureTasks(), + SubmittedTasks: mediaVideo.SubmittedTasks(), + SuspendingTasks: mediaVideo.SuspendingTasks(), + }) return res, nil } diff --git a/service/user/response.go b/service/user/response.go index 7c61a9f0..26c69694 100644 --- a/service/user/response.go +++ b/service/user/response.go @@ -33,6 +33,7 @@ type UserSettings struct { DisableViewSync bool `json:"disable_view_sync"` ShareLinksInProfile string `json:"share_links_in_profile"` AutoCompressImages bool `json:"auto_compress_images"` + AutoCompressVideos bool `json:"auto_compress_videos"` OAuthGrants []OauthGrant `json:"oauth_grants,omitempty"` } @@ -49,6 +50,7 @@ func BuildUserSettings(u *ent.User, passkeys []*ent.Passkey, parser *uaparser.Pa DisableViewSync: u.Settings.DisableViewSync, ShareLinksInProfile: string(u.Settings.ShareLinksInProfile), AutoCompressImages: u.Settings.AutoCompressImages, + AutoCompressVideos: u.Settings.AutoCompressVideos, OAuthGrants: lo.Map(grants, func(item *ent.OAuthGrant, index int) OauthGrant { return BuildOauthGrant(item) }), diff --git a/service/user/setting.go b/service/user/setting.go index d3e41334..cb3c9aa3 100644 --- a/service/user/setting.go +++ b/service/user/setting.go @@ -231,6 +231,7 @@ type ( DisableViewSync *bool `json:"disable_view_sync" binding:"omitempty"` ShareLinksInProfile *string `json:"share_links_in_profile" binding:"omitempty"` AutoCompressImages *bool `json:"auto_compress_images" binding:"omitempty"` + AutoCompressVideos *bool `json:"auto_compress_videos" binding:"omitempty"` } PatchUserSettingParamsCtx struct{} ) @@ -282,6 +283,11 @@ func (s *PatchUserSetting) Patch(c *gin.Context) error { saveSetting = true } + if s.AutoCompressVideos != nil { + u.Settings.AutoCompressVideos = *s.AutoCompressVideos + saveSetting = true + } + if s.ShareLinksInProfile != nil { u.Settings.ShareLinksInProfile = types.ShareLinksInProfileLevel(*s.ShareLinksInProfile) saveSetting = true