Skip to content

Latest commit

ย 

History

History
1508 lines (1004 loc) ยท 57 KB

README.md

File metadata and controls

1508 lines (1004 loc) ยท 57 KB

SSAFY 1ํ•™๊ธฐ ์ตœ์ข… ํ”„๋กœ์ ํŠธ(21.11.17 ~ 21.11.24)

Supported
C#ย ย  Pythonย ย  JSย ย  Preactย ย  EC2

iPhone demo Music app demo Full page demo
Full page demo
Parallax demo

โœจ Stack

์ฃผ์š” Stack

Content Main Detail
์Œ์„ฑ์ธ์‹ Python 3.9.6 / JavaScript webkitSpeechRecognition / Axios
๋“œ๋กœ์ž‰ JavaScript Canvas / Blob Data/ Axios
FE HTML / CSS / JavaScript Django 3.2.9 / HTML5 / CSS3 / ES6
ํด๋Ÿฌ์Šคํ„ฐ๋ง C# .Net Framework 4.7.2 C#(OpenCvSharp4 - V4.5.3.20210817)
RGB Calculating Python 3.9.6 Python(Math)
DB Python 3.9.6 Python(Shell_plus)
Query ์ตœ์ ํ™” Django 3.2.9 prefetch_related / annotate / filter / exclude
๋ฐฐํฌ AWS EC2(Ubuntu Server 18.04 LTS) / Cloud9 / Gunicorn / NGINX

๊ธฐ๋ณธ Stack

ent Page Description
User accounts/ ํšŒ์›๊ฐ€์ž… / ๋กœ๊ทธ์ธ / ๋กœ๊ทธ์•„์›ƒ
์˜ˆ๊ณ ํŽธ ๋ณด๊ธฐ detail/ ์˜ํ™” ํฌ์Šคํ„ฐ ๋ˆ„๋ฅผ ์‹œ, ์˜ˆ๊ณ ํŽธ ํŒ์—… ์ž๋™์žฌ์ƒ
์ƒ‰์ฑ„ ๊ณ ๋ฅด๊ธฐ choice/ ์ถ”์ฒœ ๋ฐ›๊ณ  ์‹ถ์€ ์ƒ‰์ฑ„ ๊ณ ๋ฅด๊ธฐ
ํ‰์ ์ˆœ ์ •๋ ฌ base/ TMDB ํ‰์  ์ˆœ์œผ๋กœ ์ •๋ ฌ / ํ•œ์ค„ํ‰|๋ฆฌ๋ทฐ ๋‚จ๊ธธ์‹œ ์ถ”์ฒœ ์•ˆํ•˜๋„๋ก ๊ตฌ์„ฑ
์ตœ์‹ ์ˆœ ์ •๋ ฌ base/ ๊ฐœ๋ด‰์ผ ์ตœ์‹  ์ˆœ์œผ๋กœ ์ •๋ ฌ / ํ•œ์ค„ํ‰|๋ฆฌ๋ทฐ ๋‚จ๊ธธ์‹œ ์ถ”์ฒœ ์•ˆํ•˜๋„๋ก ๊ตฌ์„ฑ
์ด์ „ ์ƒ‰์ฑ„๋กœ ์ •๋ ฌ base/ AI Z Score ์ˆœ์œผ๋กœ ์ •๋ ฌ / ํ•œ์ค„ํ‰|๋ฆฌ๋ทฐ ๋‚จ๊ธธ์‹œ ์ถ”์ฒœ ์•ˆํ•˜๋„๋ก ๊ตฌ์„ฑ
์‚ฌ์šฉ์ž ํ‰๊ท  ํ‰์  detail/ ์˜ํ™”์— ๋Œ€ํ•ด ์œ ์ €๊ฐ€ ๋‚จ๊ธด ํ‰๊ท  ํ‰์  ๊ณ„์‚ฐ ๋ฐ ์ถœ๋ ฅ
์˜ํ™” ํ‚ค์›Œ๋“œ ๊ฒ€์ƒ‰ base/ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์œผ๋กœ ์ œ๋ชฉ > ๋‚ด์šฉ์— ํ‚ค์›Œ๋“œ ํฌํ•จ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์ •๋ ฌํ•ด์„œ ์ถœ๋ ฅ
ํ€ด์ฆˆ ๋งŒ๋“ค๊ธฐ quiz_create/ ์‚ฌ์šฉ์ž๊ฐ€ ์˜ํ™”์— ๋Œ€ํ•œ ๊ทธ๋ฆผ ํ€ด์ฆˆ๋ฅผ ๋‚ผ ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑ
ํ€ด์ฆˆ ํ’€๊ธฐ quiz/ ์‚ฌ์šฉ์ž๋“ค๋ผ๋ฆฌ ๋ฌธ์ œ๋ฅผ ํ’€๊ณ  ํ‹ฐ์–ด์— ๋Œ€ํ•œ ์ ์ˆ˜๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑ
ํ‰์  ๋ฐ ํ•œ์ค„ํ‰ movie_comment/ ์˜ํ™”์— ๋Œ€ํ•œ ๋ณ„์ ๊ณผ ํ•œ์ค„ํ‰์„ ๋‚จ๊ธธ ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑ
๋ฆฌ๋ทฐ(+๊ทธ๋ฆผ) review/ ๊ทธ๋ฆผ์„ ๊ทธ๋ฆฌ๋ฉฐ ๋ฆฌ๋ทฐ๋ฅผ ๋‚จ๊ธธ ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑ
๋ฆฌ๋ทฐ ์† ๋Œ“๊ธ€ review_comment/ ๋ฆฌ๋ทฐ๋งˆ๋‹ค ๋Œ“๊ธ€์„ ๋‚จ๊ธธ ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑ
์œ ์ € ํ‹ฐ์–ด tier/ ๋ธŒ๋ก ์ฆˆ๋ถ€ํ„ฐ ๋งˆ์Šคํ„ฐ๊นŒ์ง€ ๋ฆฌ๋ทฐ ์ž‘์„ฑ, ๋ฌธ์ œ ํ’€์ด ๋“ฑ์—์„œ ์ ์ˆ˜ ํš๋“
์˜ํ™” Frame ํ™•๋Œ€ detail/ detail ํŽ˜์ด์ง€์—์„œ ์˜ํ™”์— ๋Œ€ํ•œ ํ”„๋ ˆ์ž„ ํด๋ฆญ์‹œ Modal๋กœ ํ™•๋Œ€ ์ถœ๋ ฅ

๐Ÿ“ฆ Structure

erd

final-pjt
โ”œโ”€โ”€ accounts/
โ”‚   โ”œโ”€โ”€ migrations
โ”‚		โ”‚		โ””โ”€โ”€ ...
โ”‚   โ”œโ”€โ”€ templates/accounts
โ”‚		โ”‚		โ””โ”€โ”€ ...
โ”‚   โ””โ”€โ”€ static/accounts
โ”‚				โ””โ”€โ”€ ...
โ”œโ”€โ”€ movies/
โ”‚   โ”œโ”€โ”€ migrations
โ”‚		โ”‚		โ””โ”€โ”€ ...
โ”‚   โ”œโ”€โ”€ templates/movies
โ”‚		โ”‚		โ””โ”€โ”€ ...
โ”‚   โ””โ”€โ”€ static/movies
โ”‚				โ””โ”€โ”€ ...
โ”œโ”€โ”€ final-pjt/
โ”‚		โ””โ”€โ”€ ...
โ”œโ”€โ”€ staticfiles/static
โ”‚   โ”œโ”€โ”€ css
โ”‚		โ”‚		โ””โ”€โ”€ ...
โ”‚   โ”œโ”€โ”€ favicon
โ”‚		โ”‚		โ””โ”€โ”€ ...
โ”‚   โ”œโ”€โ”€ images
โ”‚		โ”‚		โ””โ”€โ”€ ...
โ”‚   โ”œโ”€โ”€ videos
โ”‚		โ”‚		โ””โ”€โ”€ ...				
โ”‚   โ””โ”€โ”€ ...
โ”œโ”€โ”€ templates
โ”‚   โ””โ”€โ”€ base.html
โ”œโ”€โ”€ .gitignore
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ db.sqlite3
โ””โ”€โ”€ requirements.txt

๐Ÿƒ ํŒ€์› ์ •๋ณด ๋ฐ ์—…๋ฌด ๋ถ„๋‹ด ๋‚ด์—ญ

์†Œ์†

  • SSAFY 6๊ธฐ ๋Œ€์ „ 2๋ฐ˜

์ด๋ฆ„

  • ๊น€์ฃผํ˜ธ
    • ํด๋Ÿฌ์Šคํ„ฐ๋ง / RGB Calculating / Tier / Quiz / DB / ์ฟผ๋ฆฌ ์ตœ์ ํ™” / FE
  • ์ด๊ฑดํฌ
    • ์Œ์„ฑ์ธ์‹ / ๋“œ๋กœ์ž‰ / Design / Quiz / DB / FE / ์ฟผ๋ฆฌ ์ตœ์ ํ™” / ๋ฐฐํฌ

โš™๏ธ ๊ฐœ์š”

  • ๋ชฉํ‘œ ์„œ๋น„์Šค ๊ตฌํ˜„ ๋ฐ ์‹ค์ œ ๊ตฌํ˜„ ์ •๋„
    • 90%
      • ๋ชฉํ‘œ ์„œ๋น„์Šค ๊ตฌํ˜„
      • ๋ชฉํ‘œ ๋””์ž์ธ ๊ตฌํ˜„
      • ์†๋„: -10%, ์ฟผ๋ฆฌ ์ตœ์ ํ™”, Static ๋ฐ DB ๊ฒฝ๋Ÿ‰ํ™” ์™„๋ฃŒํ•˜์˜€์ง€๋งŒ, ๋“œ๋กœ์ž‰์— ํ™œ์šฉ๋˜๋Š” Media File์— ๋Œ€ํ•œ ๊ฒฝ๋Ÿ‰ํ™”๊ฐ€ ์ด๋ฃจ์–ด์ง€์ง€ ์•Š์Œ
  • ๋ชฉ์ฐจ
    • Part 1, ๊น€์ฃผํ˜ธ
    • Part 2, ์ด๊ฑดํฌ
    • ๋งˆ์น˜๋ฉฐ

๐Ÿƒ Part 1, ๊น€์ฃผํ˜ธ

๊ธฐ์กด์— ์ž˜ ์•Œ๋ ค์ง„ ์ถ”์ฒœ ์•Œ๊ณ ๋ฆฌ์ฆ˜๊ณผ๋Š” ๋‹ค๋ฅธ ์ƒˆ๋กœ์šด ๋ฐฉ๋ฒ•์„ ์ฐพ๋‹ค๊ฐ€, '์œ ์ €๊ฐ€ ๊ณ ๋ฅธ ์ƒ‰์ฑ„๋กœ ์˜ํ™”๋ฅผ ์ถ”์ฒœํ•ด ์ฃผ๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜'์„ ๊ตฌํ˜„ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

๋ฌผ๋ก  ๊ทธ๋Ÿฌํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•ด์ฃผ๋Š” ๊ณณ์€ ์ฐพ๊ธฐ ํž˜๋“ค์—ˆ๊ธฐ์—, ์šฐ๋ฆฌ๋Š” ์ง์ ‘ ์˜ํ™” ์ƒ‰์ฑ„ ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค๊ธฐ๋กœ ํ–ˆ๋‹ค.

์ƒ‰์ฑ„ ๋ฐ์ดํ„ฐ ๋งŒ๋“ค๊ธฐ

์‚ฌ์šฉ ํ”„๋กœ๊ทธ๋žจ: Visual Studio 2019

์‚ฌ์šฉ ์–ธ์–ด: C#(WPF)

์‚ฌ์šฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: OpencvSharp

๋ฌด์—‡์œผ๋กœ ๋งŒ๋“ค ๊ฒƒ์ธ๊ฐ€?

๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค๊ธฐ์— ์•ž์„œ, ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ์˜ํ™”์˜ ์ƒ‰์ฑ„๋ฅผ ๋ฐ›์•„์˜ฌ ์ง€ ์ƒ๋‹นํžˆ ๊ณ ๋ฏผํ–ˆ๋‹ค.

์ฒ˜์Œ์—” ์ƒ‰์ฑ„๋ฅผ ์ œ๊ณตํ•ด์ฃผ๋Š” API๊ฐ€ ์žˆ์„ ๊ฑฐ๋ผ ์ƒ๊ฐํ–ˆ๊ณ , ์‹ค์ œ๋กœ ๊ฒ€์ƒ‰์–ด์— ์žกํ˜”์—ˆ์ง€๋งŒ ์‹ค์ƒ์€ ๋„˜์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์—ˆ๋‹ค.

image-20211123093357607

์ƒ‰์ฑ„ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•œ๋‹ค๊ณ  ํ–ˆ์œผ๋‚˜..

๊ทธ๋ž˜์„œ ์ง์ ‘ ๋งŒ๋“ค์–ด๋ณด์ž๋Š” ๊ฒฐ๋ก ์„ ๋‚ด๋ ธ๋‹ค. ๊ทธ๋Ÿผ ๋ฌด์—‡์œผ๋กœ, ์–ด๋–ป๊ฒŒ ๋งŒ๋“ค ๊ฒƒ์ธ๊ฐ€?

์šฐ์„  ์„ธ ๊ฐ€์ง€์˜ ์•„์ด๋””์–ด๊ฐ€ ๋‚˜์™”๋‹ค.

ํฌ์Šคํ„ฐ์—์„œ ์ƒ‰์ฑ„ ์ถ”์ถœ
  • ์˜ํ™”๋ฅผ ํ•œ์žฅ์˜ ์ด๋ฏธ์ง€๋กœ ํ‘œํ˜„ํ•˜๋Š” ๋งŒํผ, ํ•ด๋‹น ์˜ํ™”๋ฅผ ๋Œ€ํ‘œํ•œ๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ์Œ

  • ๋ฐ์ดํ„ฐ ์ถ”์ถœ ๋งค์šฐ ๋น ๋ฆ„

  • ๊ทธ๋Ÿฌ๋‚˜, ์˜ํ™”์˜ ์ƒ‰์ฑ„์™€ ํฌ์Šคํ„ฐ์˜ ์ƒ‰์ฑ„๊ฐ€ ๊ฐ™์„ ๊ฒƒ์ด๋ผ๋Š” ๋ณด์žฅ์ด ์—†์Œ

    • ๊ตญ๋‚ด ํฌ์Šคํ„ฐ๋Š” ํ˜„๋ž€ํ•œ ๊ธ€๊ท€์™€ ํ•ด๋‹น ์˜ํ™”์˜ ์ˆ˜์ƒ๋ชฉ๋ก ๋“ฑ ์˜ํ™”์—์„œ ์ฃผ๋Š” ์ƒ‰์˜ ๋Š๋‚Œ์„ ์ž˜ ์‚ด๋ฆฌ์ง€ ๋ชป ํ•œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Œ
    • ํ•ด์™ธ ํฌ์Šคํ„ฐ๋Š” ๋Œ€์ฒด์ ์œผ๋กœ ๊ดœ์ฐฎ์œผ๋‚˜, ํžˆ์–ด๋กœ ๋ฌด๋น„ ๋“ฑ ์ผ๋ถ€์—์„œ๋Š” ์˜ํ™”์˜ ์ƒ‰์ฑ„๋ณด๋‹ค ์ฃผ์ธ๊ณต ์บ๋ฆญํ„ฐ์˜ ์ƒ‰์„ ๊ฐ•์กฐํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Œ
์˜ˆ๊ณ ํŽธ์—์„œ ์ƒ‰์ฑ„ ์ถ”์ถœ
  • ์ด ๋˜ํ•œ ์˜ํ™”์˜ ๋Š๋‚Œ์„ ์„ ์ œ๊ณตํ•ด์ฃผ๋Š” ๋งŒํผ, ํ•ด๋‹น ์˜ํ™”๋ฅผ ๋Œ€ํ‘œํ•œ๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ์Œ
  • ๋ฐ์ดํ„ฐ ์ถ”์ถœ ์˜ค๋ž˜ ๊ฑธ๋ฆผ
  • ์˜ํ™”์˜ ์ƒ‰์ฑ„์™€ ๊ฐ™์„ ๊ฒƒ์ด๋ผ๋Š” ๋ณด์žฅ์ด ์—†์ง€๋งŒ, ํฌ์Šคํ„ฐ๋ณด๋‹ค๋Š” ํ›จ์”ฌ ๋งŽ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณต๋ฐ›์„ ์ˆ˜ ์žˆ์Œ
๋ณธํŽธ์—์„œ ์ƒ‰์ฑ„ ์ถ”์ถœ
  • ์˜ํ™” ๊ทธ ์ž์ฒด๋ฅผ ๋‹ค์šด๋ฐ›์•„ ํ•ด๋‹น ์˜ํ™”์˜ ์™„์ „ํ•œ ์ƒ‰์ฑ„ ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•
  • ๋ฐ์ดํ„ฐ ์ถ”์ถœ ๋งค์šฐ ์˜ค๋ž˜ ๊ฑธ๋ฆผ
  • ๋ถˆ๋ฒ•์ ์ธ ๊ฒฝ๋กœ๋กœ ์˜ํ™”๋ฅผ ๋‹ค์šด๋ฐ›์•„ ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค์–ด์„  ์•ˆ๋จ
    • ๋Œ€๋ถ€๋ถ„์˜ ์˜ํ™”๋“ค์ด ์ตœ๊ทผ ๊ฐœ๋ด‰ ๋ฐ ๊ฐœ๋ด‰ ์˜ˆ์ •์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋Š” ์—†๋‹ค๊ณ  ๋ด๋„ ๋ฌด๋ฐฉ

์ด๋Ÿฌํ•œ ๊ณ ๋ฏผ์„ ๊ฑฐ์นœ ๊ฒฐ๊ณผ, ์˜ˆ๊ณ ํŽธ์„ ์‚ฌ์šฉํ•˜์ž๋Š” ๊ฒฐ๋ก ์ด ๋‚˜์™”๋‹ค.

์˜ํ™”์˜ ์ผ๋ถ€ ์žฅ๋ฉด์„ ๋‹ด๊ณ  ์žˆ์œผ๋ฉฐ, ๊ณ ๊ฐ๋“ค์—๊ฒŒ 'ํ•ด๋‹น ์˜ํ™”์˜ ์ „๋ฐ˜์ ์ธ ๋Š๋‚Œ'์„ ์ฃผ๋„๋ก ์„ค๊ณ„๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ƒ‰์ฑ„ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฝ‘๊ธฐ์— ์ ์ ˆํ•  ๊ฒƒ์ด๋ผ ์ƒ๊ฐ๋˜์—ˆ๋‹ค.

์–ด๋–ป๊ฒŒ ๋งŒ๋“ค ๊ฒƒ์ธ๊ฐ€?

์‚ฌ์šฉํ•  ๋ฐ์ดํ„ฐ๋Š” ์ •ํ–ˆ๊ณ , ์–ด๋–ป๊ฒŒ ๋งŒ๋“ค์ง€๋ฅผ ๊ณ ๋ฏผํ–ˆ๋‹ค. ์˜์ƒ์ฒ˜๋ฆฌ ๊ด€๋ จ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹ธํ”ผ ์ด์ „ ๋‹ค๋ค„๋ณธ ์ ์ด ์žˆ์—ˆ๊ธฐ์—, ์„ธ ๊ฐ€์ง€ ์ •๋„์˜ ๊ธฐ์ค€ ๋‚ด์—์„œ ๋ฐฉ๋ฒ•๋ก ์„ ๊ฒฐ์ •ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

์˜ค๋žœ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ๋ฉด ์•ˆ๋œ๋‹ค.
  • ํ•ด๋‹น ์ž‘์—…์€ ์ถ”์ฒœ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•œ ๋ฐ์ดํ„ฐ ์„ ๋ณ„๋กœ, ๋‹จ๊ธฐ๊ฐ„์— ๋๋‚ด์•ผ ํ•œ๋‹ค. ๋ฐฐ๋ณด๋‹ค ๋ฐฐ๊ผฝ์ด ์ปค์ง€๋Š” ๊ฒฝ์šฐ๋Š” ์žˆ์–ด์„  ์•ˆ๋œ๋‹ค.
๋‹ค๋ฃจ๊ธฐ ์‰ฌ์›Œ์•ผ ํ•œ๋‹ค.
  • ๋‚ด๊ฒŒ ์žˆ์–ด ํŽธํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด์•ผ ์ถ”ํ›„ ๋”ฐ๋กœ ํ”„๋กœ๊ทธ๋žจ์„ ์ œ์ž‘ํ•˜๊ณ  ๋ฐฐํฌํ•  ๋•Œ๋„ ๋ฌด๋ฆฌ๊ฐ€ ์—†์„ ๋ฟ๋”๋Ÿฌ, ๋ฐ์ดํ„ฐ ์ƒ์„ฑ์˜ ํ”ผ๋กœ๋„๋ฅผ ์ตœ์†Œ๋กœ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค.
์ž‘๋™๋ฐฉ์‹์˜ ํ†ต์ผ์„ฑ์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค.
  • ์˜ํ™” ์˜ˆ๊ณ ํŽธ๋งˆ๋‹ค ํฌ๊ธฐ์™€ ๊ธธ์ด๊ฐ€ ์ œ๊ฐ๊ฐ์ด๋‹ค. ํ•˜์ง€๋งŒ ํ†ต์ผ์„ฑ์ด ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ƒ๊ฐํ•ด๋‚ธ๋‹ค๋ฉด, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋“ฑ์„ ํ•˜์ง€ ์•Š๊ณ ๋„ ์ข‹์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฝ‘์•„๋‚ผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

์ˆ˜๋งŽ์€ ๊ณ ๋ฏผ์„ ํ•œ ๋์—, ๋‚ด๊ฒŒ ์ต์ˆ™ํ•˜๋ฉด์„œ๋„ ์ „์— ๋‹ค๋ค„๋ณธ ๊ฒฝํ—˜์„ ์‚ด๋ ค ๋นจ๋ฆฌ ๋๋‚ผ ์ˆ˜ ์žˆ์„ ๋งŒ ํ•œ ๊ตฌ์กฐ์ธ C# WPF + OpencvSharp๋ฅผ ์ด์šฉํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

ํ”„๋กœ๊ทธ๋žจ ๊ตฌ์„ฑ

visual studio 2019๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ wpf ๋นˆ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“  ํ›„, xaml์€ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋ฒ„ํŠผ์„ ๋งŒ๋“ค๊ณ  ํ•ด๋‹น ๋ฒ„ํŠผ์ด ์ œ๊ณตํ•  ์ด๋ฒคํŠธ๋ฅผ ์ฝ”๋“œ๋น„ํ•˜์ธ๋“œ์— ์ ๊ธฐ๋กœ ํ–ˆ๋‹ค.

image-20211123103730493

์ด์ „์— ํ–ˆ๋˜ ํ”„๋กœ์ ํŠธ๋“ค์ฒ˜๋Ÿผ UI๋ฅผ ๊พธ๋ฐ€ ์ƒ๊ฐ์€ ํ•˜์ง€ ์•Š์•˜๊ธฐ์—, mvvm ๊ตฌ์กฐ๋‚˜ ๋ฐ”์ธ๋”ฉ๊ฐ™์€ ๊ฑด ์—ผ๋‘ํ•˜์ง€ ์•Š์•˜๋‹ค. ์‹œ๊ฐ„์„ ์ตœ์†Œํ•œ์œผ๋กœ ์‚ฌ์šฉํ•ด์•ผ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

nuget์„ ์‚ฌ์šฉํ•ด OpencvSharp๋ฅผ ์„ค์น˜ํ–ˆ๊ณ , ์˜ํ™”๋ฅผ ์„ ํƒํ•  dialog ๋“ฑ์„ ์ฒจ๋ถ€ํ•˜์˜€๋‹ค.

OpenFileDialog openFile = new OpenFileDialog();
openFile.DefaultExt = "video&image";
openFile.Filter = "video&image|*.mov;*.mp4;*.png;";

์ดํ›„ ์˜ํ™” ์˜ˆ๊ณ ํŽธ์„ ์„ ํƒํ•˜๋ฉด ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ํ•œ์žฅ์”ฉ mat์œผ๋กœ ์˜ฎ๊ฒจ์™”๊ณ , OpencvSharp์—์„œ ์ œ๊ณตํ•˜๋Š” Kmeans๋ฅผ ์ด์šฉํ•˜์—ฌ ๋น„์ง€๋„ํ•™์Šต์ธ ํด๋Ÿฌ์Šคํ„ฐ๋ง์„ ์ˆ˜ํ–‰ํ•ด ํ•ด๋‹น ํ”„๋ ˆ์ž„์—์„œ์˜ ๋Œ€ํ‘œ์ƒ‰ 5๊ฐœ๋ฅผ ๋ฝ‘์•„ ํ•˜๋‚˜์˜ ๋ฆฌ์ŠคํŠธ์— ๊ธฐ๋กํ•˜์˜€๋‹ค.

if (openFile.ShowDialog() == true)
{
    VideoCapture video = new VideoCapture(openFile.FileName);
    int length = video.FrameCount;
    int clustersCount = 5;
    int total_x = 1920;
    List<RGB> RGB_List = new List<RGB>();  // ํ•ด๋‹น RGB ํด๋ž˜์Šค๋Š” float 3๊ฐœ๋กœ ์ด๋ฃจ์–ด์ง
    RGB RGB_row;
    Mat columnVector;

    using (Mat src = new Mat(),
           samples = new Mat(),
           bestLabels = new Mat(),
           centers = new Mat())
    {
        int cnt = 0;
        while (true)
        {
            video.Read(src);

            if (src.Empty())
                break;  // ์˜์ƒ์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๋ฌดํ•œ๋ฐ˜๋ณต

            Cv2.Blur(src, src, new OpenCvSharp.Size(15, 15));  // ๋ธ”๋Ÿฌ๋กœ ํ”„๋ ˆ์ž„์„ ๋ญ‰๊ฐฌ
            columnVector = src.Reshape(cn: 3, rows: src.Rows * src.Cols);  // ์ฐจ์›๋ณ€๊ฒฝ
            columnVector.ConvertTo(samples, MatType.CV_32FC3);

            Cv2.Kmeans(  // ํด๋Ÿฌ์Šคํ„ฐ๋ง์„ ์‚ฌ์šฉ
                data: samples,
                k: clustersCount,
                bestLabels: bestLabels,
                criteria:
                new TermCriteria(type: CriteriaTypes.Eps | CriteriaTypes.MaxIter, maxCount: 10, epsilon: 1.0),
                attempts: 3,
                flags: KMeansFlags.PpCenters,
                centers: centers);

            for (int idx = 0; idx < 5; idx++) 
            {
                RGB_row = new RGB(centers.At<float>(idx, 0), centers.At<float>(idx, 1), centers.At<float>(idx, 2));  // ๊ฒฐ๊ณผ๋กœ ๋‚˜์˜จ 5๊ฐœ์˜ ํ”„๋ ˆ์ž„ ๋Œ€ํ‘œ์ƒ‰์„ ๋ฆฌ์ŠคํŠธ์— ์ €์žฅ
                RGB_List.Add(RGB_row);
            }
            Console.WriteLine(cnt++ + "/" + length);
    ...
        }
    ...
    }
...
}

๊ธฐ๋กํ•œ RGB ๊ฐ’๋“ค์„ ํ•˜๋‚˜์˜ ์ด๋ฏธ์ง€๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๊ธฐ ์œ„ํ•ด ๊ฒ€์€์ƒ‰์˜ ๋นˆ Mat์„ ํ•˜๋‚˜ ๋งŒ๋“ค๊ณ  ์ƒ‰์„ ์ž…ํ˜€ ๋‚˜๊ฐ”๋‹ค.

int total_y = RGB_List.Count / total_x;
Mat result = new Mat(50 * (total_y + 1), total_x, MatType.CV_8UC3, new Scalar(0, 0, 0));  // ์“ฐ๋ ˆ๊ธฐ๊ฐ’์ด ๋“ค์–ด๊ฐ€๋Š” ๊ฑธ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๊ฒ€์€์ƒ‰์œผ๋กœ ์ฑ„์›Œ๋‘ 
for (int col = 0; col < RGB_List.Count; col++)
{
    int y = col / total_x;
    int x = col % total_x;
    var newPixel = new Vec3b  // ์ˆœ์„œ๋Š” RGB๊ฐ€ ์•„๋‹Œ BGR์ธ ๊ฒƒ์— ์ฃผ์˜
    {
        Item0 = (byte)RGB_List[col].B, // B
        Item1 = (byte)RGB_List[col].G, // G
        Item2 = (byte)RGB_List[col].R // R
    };
    for (int row = 0; row < 50; row++)
    {
        result.Set(50 * y + row, x, newPixel);
    }
    Console.WriteLine(col + "/" + RGB_List.Count);
}

๊ทธ ํ›„ ํ•ด๋‹น Mat์„ ์ด๋ฏธ์ง€๋กœ ์ €์žฅํ•˜์˜€๋‹ค.

int lastIndex = openFile.FileName.LastIndexOf('.');
string filename = openFile.FileName.Substring(0, lastIndex) + ".png";
Cv2.ImWrite(filename, result);
MessageBox.Show("์ด๋ฏธ์ง€ ์ถ”์ถœ ์™„๋ฃŒ");

image-20211118161641205

ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘๋™์‹œํ‚ค๋ฉด ํ”„๋ ˆ์ž„์— ๋”ฐ๋ฅธ ์ง„ํ–‰์ƒํ™ฉ์ด ์ฝ˜์†”์— ์ž‘์„ฑ๋˜์–ด ๋‚˜์˜จ๋‹ค.

Image Pasted at 2021-11-17 13-52

์˜ˆ๊ณ ํŽธ์—์„œ ๋ฝ‘์•„๋‚ธ ํ”„๋ ˆ์ž„๋“ค์˜ ๋Œ€ํ‘œ์ƒ‰์„ ๊ธฐ๋กํ•œ ํ•œ ์žฅ์˜ ์ด๋ฏธ์ง€(์•ž์œผ๋กœ ์ด๋Ÿฌํ•œ ์ด๋ฏธ์ง€๋ฅผ ์ปฌ๋Ÿฌ๋ฐ”์ฝ”๋“œ๋ผ ์นญํ•˜๊ฒ ๋‹ค).

์˜ํ™” ์˜ˆ๊ณ ํŽธ์„ ๋‹ค์šด๋ฐ›์œผ๋ฉฐ ๊ณ„์†ํ•ด์„œ ํ”„๋กœ๊ทธ๋žจ์„ ๋Œ๋ ธ๋‹ค. ์˜ˆ๊ณ ํŽธ์„ ๋‹ค์šด๋ฐ›๋Š” ๊ฒƒ๋„, ํ”„๋กœ๊ทธ๋žจ์„ ๋Œ๋ฆฌ๋Š”๊ฒƒ๋„ ๊ต‰์žฅํžˆ ํ”ผ๊ณคํ•œ ์ผ์ด์—ˆ์œผ๋‚˜ ๋˜๋„๋ก ๋นจ๋ฆฌ ๋๋‚ด๊ธฐ ์œ„ํ•ด ์‰ด ํ‹ˆ ์—†์ด ์ž‘์—…ํ–ˆ๋‹ค.

์˜ˆ๊ณ ํŽธ๋“ค์„ ํ•œ ๋ฒˆ์— ์„ ํƒํ•ด์„œ ๋ชจ๋“  ์ปฌ๋Ÿฌ๋ฐ”์ฝ”๋“œ๊ฐ€ ์ถ”์ถœ๋  ๋•Œ๊นŒ์ง€ ๋Œ๋ฆฌ๋Š” ๋ฐฉ๋ฒ•๋„ ์ƒ๊ฐํ–ˆ์œผ๋‚˜, ์˜ˆ์ „์— ๋น„์Šทํ•œ ์ž‘์—…์„ ํ•˜๋‹ค๊ฐ€ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ผฌ์—ฌ์„œ ์—‰๋ง์ด ๋œ ๊ฒฝํ—˜์ด ์žˆ๊ธฐ์— ๋‚ด๊ฐ€ ์กฐ๊ธˆ ๊ณ ์ƒํ•˜๋”๋ผ๋„ ํ•˜๋‚˜์”ฉ ์ง„ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํƒํ–ˆ๋‹ค.

์ค‘๊ฐ„์ค‘๊ฐ„ ํ”„๋กœ๊ทธ๋žจ์ด ๋‹ค์šด๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ๋‹ค. ์˜ˆ๊ณ ํŽธ์˜ ์ฝ”๋ฑ ์ฐจ์ด ๋“ฑ์œผ๋กœ Mat ๋ณ€ํ™˜์ด ๋˜์ง€ ์•Š๋Š” ๋“ฏ ํ–ˆ๋‹ค. ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ์—์„œ ๋‹ค์šด๋ฐ›์•„ ์žฌ์‹œ์ž‘์„ ํ•˜๊ธฐ๋„ ํ–ˆ๋‹ค.

์˜ํ™” ๋ฐ์ดํ„ฐ๋ฅผ 100๊ฐœ ์ €์žฅํ•˜๊ธฐ๋กœ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, ์˜ˆ๊ณ ํŽธ 100ํŽธ์—์„œ ์ปฌ๋Ÿฌ๋ฐ”์ฝ”๋“œ 100๊ฐœ๋ฅผ ์ถ”์ถœํ–ˆ๋‹ค. ๊ทธ ๋’ค์—” ์ปฌ๋Ÿฌ๋ฐ”์ฝ”๋“œ์—์„œ ๋Œ€ํ‘œ์ƒ‰์„ ๋ฝ‘์•„ ์˜ํ™”์˜ ์ƒ‰์ฑ„ ํŒ”๋ ˆํŠธ๋กœ ์ง€์ •ํ•œ ํ›„, JsonํŒŒ์ผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ DB ๋ฐ์ดํ„ฐ ์ž‘์—…์ด ์ˆ˜์›”ํ•ด์ง€๋„๋ก ํ–ˆ๋‹ค.

var jsonData = System.IO.File.ReadAllText(filePath);
var colorList = JsonConvert.DeserializeObject<List<Color>>(jsonData) ?? new List<Color>();

๊ทธ ํ›„ Kmeans๋กœ ๋Œ€ํ‘œ์ƒ‰ 6๊ฐœ๋ฅผ ๋ฝ‘์•˜๋‹ค.

์˜ํ™” ์˜ˆ๊ณ ํŽธ์€ ๊ธด๋ฐ•๊ฐ์ด๋‚˜ ํ™”๋ฉด์ „ํ™˜ ํšจ๊ณผ๋ฅผ ์ฃผ๊ธฐ ์œ„ํ•ด ๊ฒ€์€ ํ™”๋ฉด์„ ๋„์šฐ๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•˜๋‹ค. ์ด๋ฅผ ์œ„ํ•ด 6๊ฐœ์˜ ๋Œ€ํ‘œ์ƒ‰ ์ค‘, ๊ฐ€์žฅ ์–ด๋‘์šด ๋Œ€ํ‘œ์ƒ‰์„ ์ œ์™ธํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํƒํ–ˆ๋‹ค.

int[,] total_color = new int[6, 3];

int darkest_index = 0;
int darkest_value = 255 * 3;
for (int i = 0; i < 6; i++)
{
    total_color[i, 0] = (int)centers.At<float>(i, 0);
    total_color[i, 1] = (int)centers.At<float>(i, 1);
    total_color[i, 2] = (int)centers.At<float>(i, 2);
  
    // ๊ฐ€์žฅ ์–ด๋‘์šด ์ƒ‰์„ ์ง€๋‹Œ ๋Œ€ํ‘œ์ƒ‰์„ ์ฐพ์Œ
    if (darkest_value > total_color[i, 0] + total_color[i, 1] + total_color[i, 2])
    {
        darkest_value = total_color[i, 0] + total_color[i, 1] + total_color[i, 2];
        darkest_index = i;
    }
}

// ํ•ด๋‹นํ•˜๋Š” ๊ฐ’์„ 6๋ฒˆ์งธ ๋Œ€ํ‘œ์ƒ‰์œผ๋กœ ๋ฐ”๊ฟ”์คŒ
total_color[darkest_index, 0] = total_color[5, 0];
total_color[darkest_index, 1] = total_color[5, 1];
total_color[darkest_index, 2] = total_color[5, 2];

colorList.Add(new Color()
              {
                  movie = openFile.FileNames[idx],
                  color_1_B = total_color[0, 0],
                  color_1_G = total_color[0, 1],
                  color_1_R = total_color[0, 2],
                  color_2_B = total_color[1, 0],
                  color_2_G = total_color[1, 1],
                  color_2_R = total_color[1, 2],
                  color_3_B = total_color[2, 0],
                  color_3_G = total_color[2, 1],
                  color_3_R = total_color[2, 2],
                  color_4_B = total_color[3, 0],
                  color_4_G = total_color[3, 1],
                  color_4_R = total_color[3, 2],
                  color_5_B = total_color[4, 0],
                  color_5_G = total_color[4, 1],
                  color_5_R = total_color[4, 2],
              });

// json ์—…๋ฐ์ดํŠธ
jsonData = JsonConvert.SerializeObject(colorList);
System.IO.File.WriteAllText(filePath, jsonData);

์ด๋ ‡๊ฒŒ ํ•˜์—ฌ ์˜ํ™”์˜ ์ƒ‰์ฑ„ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜์˜€๋‹ค.

์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌ์„ฑ ๋ฐ ์›นํŽ˜์ด์ง€ ์ž‘์—…

์ƒ‰์ฑ„ ์ถ”์ฒœ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋ฐ ๊ธฐํƒ€ ์•Œ๊ณ ๋ฆฌ์ฆ˜๊ณผ ์›นํŽ˜์ด์ง€ ๋””ํ…Œ์ผ ์ž‘์—…

๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์˜ค๋‹ˆ ํŽ˜์–ด์ด์‹  ๊ฑดํฌ๋‹˜์ด ๋Œ€๋ถ€๋ถ„์˜ ์›น ๊ตฌ์กฐ๋ฅผ ์žก์•„๋†“์œผ์‹  ์ƒํƒœ์˜€๋‹ค. ๋‹จ๊ธฐ๊ฐ„์— ๋๋‚ด์‹ค ์ค„ ๋ชฐ๋ž์–ด์„œ ๊ต‰์žฅํžˆ ๋†€๋ผ์› ๊ณ , ํ•ด๋‹น ๋ฐฉํ–ฅ์œผ๋กœ๋Š” ๋„์›€๋“œ๋ฆฐ ๊ฒŒ ์—†์–ด์„œ ์ฃ„์†กํ–ˆ๋‹ค.

์ƒ‰์ฑ„ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌ์„ฑ

์งœ์—ฌ์ง„ ๊ตฌ์กฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์ž‘์„ฑํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค. ์šฐ์„  ์˜ํ™” ์ƒ‰์ฑ„์— ๋Œ€ํ•œ RGB๋Š” Json ๋ฐ shell_plus๋กœ ๋“ค์–ด๊ฐ€ ์žˆ๋Š” ์ƒํƒœ์˜€๋‹ค. ์ด์ œ ์ด ๊ฐ’๋“ค๊ณผ ์œ ์ €๊ฐ€ ๊ณ ๋ฅธ ์ƒ‰์„ ๋Œ€์กฐํ•˜์—ฌ ์ •๋ ฌํ•˜๋ฉด ๋˜๋Š” ์ƒํƒœ์˜€๋Š”๋ฐ, ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ๋Œ€์กฐํ•˜๋ฉฐ ์ง„ํ–‰ํ•ด์•ผ ํ•  ์ง€ ์กฐ๊ธˆ ๋‚œ๊ฐํ–ˆ๋‹ค.

์•„์ด๋””์–ด ํšŒ์˜๋ฅผ ์ง„ํ–‰ํ•˜๋ฉฐ ๋ฐฉ๋ฒ•๋ก ์„ ๊ณ ๋ฏผํ–ˆ๋‹ค.

5๊ฐœ์˜ ์ƒ‰์ฑ„ ํ‰๊ท ์„ ๊ตฌํ•ด ๋Œ€์กฐ?
  • ํ‰๊ท ์„ ๊ตฌํ•œ๋‹ค๋ฉด ๊ธฐ์กด ์ƒ‰์ฑ„์˜ ์˜๋ฏธ๋ฅผ ์žŠ๊ฒŒ ๋  ์ˆ˜ ์žˆ๋‹ค.
  • (40, 40, 40), (60, 60, 60)๊ณผ (0, 10, 20), (100, 90, 80)์ด ์ฃผ๋Š” ์ƒ‰์ฑ„๋Š” ๋‹ค๋ฅด๋‹ค.
  • ํ‰๊ท ์„ ๋‚ด๋ฒ„๋ฆฌ๋ฉด ์ƒ‰์ฑ„ ๊ตฌ์กฐ๊ฐ€ ๊นจ์–ด์ง€๊ณ , ์•ž์„œ ๊ตฌํ•œ ๋ฐ์ดํ„ฐ๋“ค์ด ๋ฌด์šฉ์ง€๋ฌผ ๋  ์ˆ˜ ์žˆ๋‹ค.
์œ ์ €์˜ ์ƒ‰๊ณผ ๊ฐ€์žฅ ํก์‚ฌํ•œ ์ƒ‰์„ ์ง€๋‹Œ ์ˆœ์œผ๋กœ ์ •๋ ฌ?
  • ์•ž์„œ ๊ตฌํ•œ ์ƒ‰์ฑ„๋“ค์€ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค. ์–ด๋–ค ์ƒ‰์ด ์˜ํ™”์˜ ๋Œ€๋ถ€๋ถ„์„ ์ฐจ์ง€ํ•˜๋Š”์ง€๋Š” ๋ชจ๋ฅธ๋‹ค. ๊ทธ์ € ๋งŽ์ด ๋‚˜์˜จ ์ƒ‰ 5๊ฐœ๋ฅผ ์ˆœ์„œ์—†์ด ๊ณจ๋ž์„ ๋ฟ.
  • ์œ ์ €๊ฐ€ ์ฃผํ™ฉ์ƒ‰์„ ๊ณจ๋ž๋Š”๋ฐ, ๋Œ€๋ถ€๋ถ„ ๋ถ‰์€ ์ƒ‰์ฑ„๋ฅผ ์ง€๋‹Œ ์˜ํ™”๋ณด๋‹ค ์ฃผํ™ฉ ํ•˜๋‚˜๋งŒ ์ผ์น˜ํ•˜๊ณ  ๋‚˜๋จธ์ง€๋Š” ์ „ํ˜€ ๋‹ค๋ฅธ ์ƒ‰์ฑ„๋ฅผ ์ง€๋‹Œ ์˜ํ™”๊ฐ€ ๋งจ ์•ž์œผ๋กœ ์˜ค๋ฉด ์ด๋Š” ์šฐ๋ฆฌ์˜ ์˜๋„์™€ ๋งž์ง€ ์•Š๊ฒŒ ๋œ๋‹ค.
์œ ์ €์˜ ์ƒ‰๊ณผ ์˜ํ™” ์ƒ‰์ฑ„์˜ distance๋กœ ์ •๋ ฌ?
  • ์œ ์ €๊ฐ€ ๊ณ ๋ฅธ ์ƒ‰๊ณผ ์˜ํ™”์˜ ์ƒ‰์„ ํ•˜๋‚˜์”ฉ ๋น„๊ตํ•˜๋ฉฐ ๊ทธ ์ฐจ์ด๋งŒํผ ์ ์ˆ˜๋ฅผ ๋Š˜๋ฆฐ๋‹ค.
  • ์ ์ˆ˜๊ฐ€ ๊ฐ€์žฅ ๋‚ฎ๊ฒŒ ๋‚˜์˜จ ์˜ํ™”๋Š” ์œ ์ €์˜ ์ƒ‰๊ณผ distance๊ฐ€ ๊ฐ€์žฅ ์งง๋‹ค.
  • ํ•ด๋‹น ๋ฐฉ์‹์ด ๋ฐ์ดํ„ฐ๋„ ์ œ๋Œ€๋กœ ์‚ด๋ฆด ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์šฐ๋ฆฌ๊ฐ€ ์ƒ์ƒํ•œ ๋ฐฉ์‹๊ณผ ๊ฐ€์žฅ ๋‹ฎ์•„์žˆ๋‹ค.

๊ทธ๋ฆฌํ•˜์—ฌ 3๋ฒˆ์งธ ๋ฐฉ์‹์„ ํƒํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

last_color_rgb = last_color.color[4:-1].split(", ")            # Users R, G, B list

for movie in movies:
    movie_color = movie.color_set.all()[0]
    score = math.sqrt(
        math.pow(int(last_color_rgb[0]) - int(movie_color.color_1_R), 2) +\
        math.pow(int(last_color_rgb[1]) - int(movie_color.color_1_G), 2) +\
        math.pow(int(last_color_rgb[2]) - int(movie_color.color_1_B), 2)
    ) + \
    math.sqrt(
        math.pow(int(last_color_rgb[0]) - int(movie_color.color_2_R), 2) +\
        math.pow(int(last_color_rgb[1]) - int(movie_color.color_2_G), 2) +\
        math.pow(int(last_color_rgb[2]) - int(movie_color.color_2_B), 2)
    ) + \
    math.sqrt(
        math.pow(int(last_color_rgb[0]) - int(movie_color.color_3_R), 2) +\
        math.pow(int(last_color_rgb[1]) - int(movie_color.color_3_G), 2) +\
        math.pow(int(last_color_rgb[2]) - int(movie_color.color_3_B), 2)
    ) + \
    math.sqrt(
        math.pow(int(last_color_rgb[0]) - int(movie_color.color_4_R), 2) +\
        math.pow(int(last_color_rgb[1]) - int(movie_color.color_4_G), 2) +\
        math.pow(int(last_color_rgb[2]) - int(movie_color.color_4_B), 2)
    ) + \
    math.sqrt(
        math.pow(int(last_color_rgb[0]) - int(movie_color.color_5_R), 2) +\
        math.pow(int(last_color_rgb[1]) - int(movie_color.color_5_G), 2) +\
        math.pow(int(last_color_rgb[2]) - int(movie_color.color_5_B), 2)
    )
    res_movies.append([movie, int(score)])

์ฝ”๋“œ๋กœ ๋ณด๋ฉด ์กฐ๊ธˆ ์–ด์ง€๋Ÿฌ์šธ ์ˆ˜ ์žˆ์œผ๋‚˜, ์ˆ˜์‹์œผ๋กœ ๋ณด๋ฉด ๋ณ„ ๊ฒƒ ์•„๋‹Œ ๋ชจ์–‘์ƒˆ๋‹ค.

์ดํ›„ ํ•ด๋‹น ๊ฐ’๋“ค์„ ์‚ฌ์šฉํ•˜์—ฌ ์œ ์ €๊ฐ€ ๊ณ ๋ฅธ ์ƒ‰๊ณผ ์˜ํ™”์˜ ์ƒ‰์ฑ„๊ฐ€ ๊ฐ€์žฅ ๋น„์Šทํ•œ ์˜ํ™” ์ˆœ์œผ๋กœ ์ •๋ ฌํ•˜์—ฌ ํ™”๋ฉด์— ๋„์›Œ์ฃผ๋Š” ๋ฐฉ๋ฒ•์„ ํƒํ–ˆ๋‹ค.

res_movies.sort(key= lambda x: x[1])

image-20211123114402946

ํŒŒ๋ž€์ƒ‰์„ ๊ณจ๋ž์„ ๋•Œ์˜ ํ™”๋ฉด. ๋Œ€๋ถ€๋ถ„ ํŒŒ๋ž€ ์ƒ‰์ฑ„ ์˜ํ™”์ธ ์˜ํ™”๊ฐ€ ๋งจ ์œ„๋กœ ๋‚˜์˜ค๊ฒŒ ๋œ๋‹ค.

image-20211123115001045

๋ฐ˜๋Œ€๋กœ ๋ถ‰์€ ์ƒ‰์„ ๊ณจ๋ž์„ ๋•Œ์˜ ํ™”๋ฉด.

์ปฌ๋Ÿฌ๋ฐ”์ฝ”๋“œ modal๋กœ ๊ฐ•์กฐ

๋ฉ”์ธํ™”๋ฉด์—์„œ๋Š” ์ปฌ๋Ÿฌ๋ฐ”์ฝ”๋“œ๋ฅผ ์ผ์ •ํ•œ ํฌ๊ธฐ์— ๋งž๊ฒŒ ๋ณด์—ฌ์ฃผ๊ธฐ ๋•Œ๋ฌธ์—, ์ „์ฒด์ ์ธ ์ปฌ๋Ÿฌ๋ฐ”์ฝ”๋“œ์˜ ๊ตฌ์กฐ๋ฅผ ์•Œ๊ธฐ ํž˜๋“ค๋‹ค. detail์—์„œ๋Š” ์กฐ๊ธˆ ํฌ๊ฒŒ ๋ณด์—ฌ์ฃผ๊ธด ํ•˜์ง€๋งŒ, ๋ญ”๊ฐ€ ์œ ์ €์—๊ฒŒ ํ™•์‹คํžˆ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์—ˆ๋‹ค. ์˜ํ™” ์ƒ‰์ฑ„๋ฅผ ๊ทธ๋ƒฅ ๋ฝ‘์•˜๋‹ค๋Š” ๊ฒƒ์ด ์•„๋‹Œ, ํ•˜๋‚˜์˜ ์ฆ๊ฑฐํ’ˆ์ด์ž ๊ฒฐ๊ณผ๋‹ˆ๊นŒ.

์šฐ์„  base์— modal๊ณผ js ์˜ ์œ„์น˜์— block์„ ์žก์•˜๋‹ค. modal์€ ํ•ด๋‹น ์›น์„ ์ž ์‹œ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ณ  modal์ด ํ™œ์„ฑํ™”๋˜๋Š” ๊ตฌ์กฐ์ธ๋ฐ, modal์ด ์›น ๊ตฌ์กฐ ์ค‘ ์•ˆ์ชฝ์— ์œ„์น˜ํ•  ๊ฒฝ์šฐ ์›น๊ณผ modal ๋‘˜๋‹ค ๋น„ํ™œ์„ฑํ™”๊ฐ€ ๋˜์–ด ์ƒˆ๋กœ๊ณ ์นจ์ด ์•„๋‹ˆ๋ฉด modal์„ ๋Œ ์ˆ˜ ์—†๊ฒŒ๋” ๋˜์–ด๋ฒ„๋ ธ๋‹ค. ์ด๋Š” ์˜ˆ์ „ pjt03์„ ์ง„ํ–‰ํ•  ๋•Œ์™€ ๊ฐ™์€ ๋ฌธ์ œ์˜€๋‹ค. ๊ทธ๋ ‡๊ธฐ์— ์˜ˆ์ „์ฒ˜๋Ÿผ modal์˜ ์œ„์น˜๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ๋ฐ”๋กœ ์•Œ์•˜๋‹ค.

๋˜ํ•œ ์ปฌ๋Ÿฌ๋ฐ”์ฝ”๋“œ๋ฅผ ํด๋ฆญํ–ˆ์„ ๋•Œ ์–ป์–ด์ง€๋Š” ๋ฐ์ดํ„ฐ๋Š” ์›น ์•ˆ์ชฝ์— ์œ„์น˜ํ•˜๋Š”๋ฐ, ์ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”๊นฅ์ชฝ์˜ modal๊นŒ์ง€ ์ „๋‹ฌํ•ด์ฃผ๋ ค๋ฉด js๊ฐ€ ๊ฐ€์žฅ ๊ฐ„ํŽธํ•œ ๋ฐฉ๋ฒ•์ด๋ผ ์ƒ๊ฐํ•ด ํ•ด๋‹น ๊ตฌ๋ฌธ์„ ์ž‘์„ฑํ•˜์˜€๋‹ค.

{% block context %}
<div class="d-flex justify-content-center align-items-center" style="height:100px;" data-toggle="modal" data-target="#imagemodal">
            <img src="{{color.color_url}}" alt="" class="h-75 w-100" id="color_url">
          </div>
...
{% endblock context %}

{% block modal %}
<div class="modal" id="imagemodal">
  <div class="modal-dialog" style="max-width: 80%; vertical-align: middle;">
    <div class="modal-content" style="border-radius: 30px; background-color: rgba(255,255,192,0.1); backdrop-filter: blur(10px); box-shadow: 2px 7px 15px 8px rgba(0,0,0,0.3);">
      
      <!-- Modal body -->
      <div class="modal-body">
        <img src="" alt="">
      
    </div>
  </div>
</div>
{% endblock modal %}'

{% block js %}
  <script>
    const color_url = document.querySelector('#color_url')
    const src = color_url.getAttribute('src')
    const imagemodal = document.querySelector('#imagemodal')
    const color_barcode = imagemodal.querySelector('.modal-body img')
    color_url.addEventListener('click', function(event) {
      color_barcode.src = src
    })
  </script>
{% endblock js %}

image-20211123133120638

๋ฐ”์ฝ”๋“œ๋ฅผ ํด๋ฆญํ•˜๋ฉด ํฌ๊ฒŒ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ๋” modal์„ ์ž‘์„ฑ

์ƒ‰ ๊ธฐ๋ก ํด๋ฆญํ•˜๋ฉด ์ตœ์‹  ํƒ์ƒ‰์œผ๋กœ ์˜ฌ๋ ค ๋ณด์—ฌ์ฃผ๊ธฐ

๋ฐ”๋กœ ์œ„์˜ ์ด๋ฏธ์ง€ ์™ผ์ชฝ์„ ์ž์„ธํžˆ ๋ณด๋ฉด, ์œ ์ €์˜ ์ƒ‰ ์„ ํƒ ๊ธฐ๋ก์ด ์žˆ๋‹ค. ๋งŒ์•ฝ ์œ ์ €๊ฐ€ ์ € ์ƒ‰์— ๋Œ€ํ•œ ๊ฐ’์„ ๋‹ค์‹œ ๋ณด๊ณ  ์‹ถ์–ดํ•œ๋‹ค๋ฉด? ๊ณผ์—ฐ ์˜ˆ์ „๊ณผ ๋˜‘๊ฐ™์€ RGB๋ฅผ ์™„๋ฒฝํžˆ ๊ณ ๋ฅผ ์ˆ˜ ์žˆ์„๊นŒ? ํ•˜๋Š” ์ƒ๊ฐ์— '๊ณผ๊ฑฐ ๊ธฐ๋ก์„ ๋ˆ„๋ฅด๋ฉด ๋‹ค์‹œ ํ•œ ๋ฒˆ ํƒ์ƒ‰'ํ•ด์ฃผ๋Š” ์‹œ์Šคํ…œ์„ ๊ตฌ์„ฑํ•˜์˜€๋‹ค.

๋กœ์ง ์ž์ฒด๋Š” ๊ฐ„๋‹จํ•  ์ˆ˜ ์žˆ์œผ๋‚˜, ํ˜„์žฌ์˜ ๋‹ค๋ฅธ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌ์กฐ๋“ค์„ ๊นจ๋œจ๋ฆฌ์ง€ ์•Š๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ž‘์„ฑํ•ด์•ผ ํ–ˆ๋‹ค. ์šฐ์„  ์ƒ‰ ์ถ”์ฒœ ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ ์œ ์ €์˜ ์ƒ‰์„ ๋ฐ›์•„ ๊ฐ€์žฅ ์ตœ๊ทผ์— ๊ณ ๋ฅธ ๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ ๋ณด์—ฌ์ฃผ๋Š”๋ฐ, ๊ทธ๋ ‡๋‹ค๋ฉด ์šฐ๋ฆฌ์˜ ๋กœ์ง์€ '์„ ํƒํ•œ ๊ฐ’์„ ๊ฐ€์žฅ ์ตœ๊ทผ ๊ฐ’์œผ๋กœ ์˜ฎ๊ธด๋‹ค'๋กœ ํ˜๋Ÿฌ๊ฐ€์•ผ ํ–ˆ๋‹ค.

์šฐ์„  ํ•ญ๋ชฉ์„ ๋ˆŒ๋ €์„ ๋•Œ ํ•ด๋‹นํ•˜๋Š” ์ƒ‰ ๊ฐ’์„ ์•Œ์•„๋ณด๊ธฐ ์œ„ํ•ด js๋ฅผ ์ด์šฉํ•˜์—ฌ ์ฝ˜์†”์ฐฝ์— ์ฐ์–ด ๋ณด์•˜๋‹ค. ๋‹คํ–‰ํžˆ backgroundcolor๋กœ ๋“ค๊ณ  ์žˆ๋Š” ๊ฒƒ์ด ํ™•์ธ๋˜์—ˆ๋‹ค.

๋‹ค์Œ์€ ํ•ด๋‹น ํ•ญ๋ชฉ ๋ฐ‘์— aํƒœ๊ทธ๋ฅผ ๋งŒ๋“ค์–ด ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ํ•จ์ˆ˜๋กœ ์ด๋™์‹œ์ผœ ์ฃผ๊ธฐ๋กœ ํ–ˆ๋‹ค.

ํ•จ์ˆ˜๋Š” ๊ฐ„๋‹จํ•˜๊ฒŒ ์ธ์ž๋กœ rgb ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ๊ฐ€์žฅ ์ตœ๊ทผ๊ฐ’์œผ๋กœ ์˜ฎ๊ฒจ์ฃผ๋Š” ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

def usercolor_update(request, rgb):
    colors = request.user.usercolorrecord_set.all()
    for i in range(len(colors)):
        if colors[i].color == rgb:
            colors[i].delete()
            break
    UserColorRecord.objects.create(user=request.user, color=rgb)  # Color Push
    return redirect('movies:index', 1)

ํ•˜์ง€๋งŒ ์ด๋Š” ์„œ๋ฒ„ ๊ฐ’์„ ๋ฐ”๊พธ๋Š” ํ–‰์œ„์ด๋ฏ€๋กœ, post๋กœ ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ์ด ์˜ณ์€ ์„ ํƒ์ด์—ˆ๋‹ค. aํƒœ๊ทธ๋ฅผ ์—†์• ๊ณ  form์„ ๊ตฌ์ถ•ํ•œ ๋’ค์—, ๋‚ด๋ถ€ ๋ฒ„ํŠผ์„ ๋งŒ๋“ค์–ด์„œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ตฌ์กฐ๋กœ ๋ฐ”๊พธ์—ˆ๋‹ค.

<form action="{% url 'movies:usercolor_update' color.color %}" method="POST">
    {% csrf_token %}
    <button style="width: 100%; height: 100%; background-color: transparent; background-repeat: no-repeat; border: none; cursor: pointer; overflow: hidden; outline: none;"/>
</form>

๋Œ€๋ถ€๋ถ„์˜ ์ž‘์—…์„ ๋๋‚ด๊ณ  ์ฟผ๋ฆฌ ์ตœ์ ํ™”๋ฅผ ์ง„ํ–‰ํ–ˆ๋‹ค. ์ƒ‰์ฑ„ ์ถ”์ฒœ๋งŒ ํ•ด๋„ ์ฟผ๋ฆฌ๊ฐ€ 400๊ฐœ๊ฐ€๋Ÿ‰์ด์—ˆ๋‹ค. ํ•ด๋‹น ์ถ”์ฒœ๋“ค์€ ๋ชจ๋‘ '์œ ์ €๊ฐ€ ๋ณ„์ ์„ ๋‚จ๊ธด ์˜ํ™”๋ฅผ ์ถ”์ฒœํ•˜์ง€ ์•Š์Œ'์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋˜์–ด์ ธ ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ด ๋ถ€๋ถ„๋ถ€ํ„ฐ ํ•ด๊ฒฐํ•ด์•ผ ํ–ˆ๋‹ค.

movies = []
for movie in temp_movies:
    for comment in movie.comments.all():
        if request.user == comment.user:
            break
        else:
            for review in movie.reviews.all():
                if request.user == review.user:
                    break
                else:
                    movies.append(movie)

๊ธฐ์กด ์ฝ”๋“œ. for๋ฌธ๊ณผ ์—ฐ๊ณ„๋˜์–ด ์žˆ์–ด ์ฟผ๋ฆฌ๋ฅผ ๊ณ„์† ๋ฐ›์•„์˜ค๋Š” ๊ตฌ์กฐ์˜€๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด for๋ฌธ๊ณผ ์—ญ์ฐธ์กฐ๋ฅผ ๊ฐ™์ด ๋‘๋Š” ๊ฒŒ ์•„๋‹Œ, ์ฟผ๋ฆฌ๋ฅผ ํ•„ํ„ฐ๋กœ ์ œํ•œํ•˜์—ฌ ๋ฐ›์•„์˜จ ํ›„ for๋ฌธ์œผ๋กœ ํ•ด๋‹น ์œ ์ €๊ฐ€ ๋ฆฌ๋ทฐ ํ˜น์€ ํ•œ์ค„ํ‰์„ ๋‚จ๊ธด ์˜ํ™”์˜ pk๊ฐ’์„ set์œผ๋กœ ์ •๋ฆฌํ•˜์˜€๋‹ค. ๊ทธ ํ›„ ์˜ํ™” ๋ชฉ๋ก์„ ๋ฐ›์•„์˜ฌ ๋•Œ ์ œ์™ธํ•  ๋ชฉ๋ก์œผ๋กœ ๋„ฃ์–ด ์ฃผ์—ˆ๋‹ค.

already_watched_movies = set()

users_moviecomments = MovieComment.objects.filter(user=request.user.pk).values('movie')
for users_moviecomment in users_moviecomments:
    already_watched_movies.add(users_moviecomment['movie'])

users_reviews = Review.objects.filter(user=request.user.pk).values('movie')
for users_review in users_reviews:
    already_watched_movies.add(users_review['movie'])
    
movies = Movie.objects.exclude(id__in=already_watched_movies)

์œ„ ์ฝ”๋“œ ์ดํ›„ ์ฟผ๋ฆฌ๊ฐ€ ์•ฝ 200๊ฐœ๋กœ, ์ ˆ๋ฐ˜ ๊ฐ€๋Ÿ‰์ด ๊ฐ์†Œํ•˜๋Š” ํšจ๊ณผ๋ฅผ ๋‚ณ์•˜๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์•„์ง ์ ์€ ์ˆ˜์˜ ์ฟผ๋ฆฌ๋Š” ์•„๋‹ˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ์˜ํ™” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ฌ ๋•Œ ํ•ด๋‹นํ•˜๋Š” ์˜ํ™”์˜ ์ƒ‰์ฑ„ ๋ฐ์ดํ„ฐ๊นŒ์ง€ ๋“ค๊ณ ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ๋‹ค.

movies = Movie.objects.exclude(id__in=already_watched_movies).prefetch_related('color_set', 'comments')

์†ก์ˆ˜์‹  ์ฟผ๋ฆฌ 8๊ฐœ, ํ•œ์ž๋ฆฌ ์ˆ˜๋กœ ์ค„์–ด๋“œ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๐Ÿƒ Part 2, ์ด๊ฑดํฌ

SSAFY 1ํ•™๊ธฐ์˜ ๋งˆ๋ฌด๋ฆฌ, SSAFY์—์„œ ์ฒ˜์Œํ•˜๋Š” ํ”„๋กœ์ ํŠธ,

์ž˜ํ•˜๊ณ  ์‹ถ์—ˆ๋‹ค. ์›ํ•˜๋Š” ๋ชจ๋“  ๊ฒƒ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์ง‘์ค‘ํ–ˆ๋‹ค.

์Œ์„ฑ์ธ์‹

๊ธฐ์กด์—๋Š” ๋„ค์ด๋ฒ„, ์นด์นด์˜ค ๋‘˜ ์ค‘ ํ•˜๋‚˜์˜ API๋ฅผ ์‚ฌ์šฉํ•ด์„œ Axios, Promise ๋ฐฉ์‹์œผ๋กœ API / Django View 2๊ฐœ์™€ ํ†ต์‹ ํ•˜์—ฌ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋ ค๊ณ  ๊ตฌ์ƒ

ํ•˜์ง€๋งŒ, IE์™€ Chrome์—์„œ ๋™์ž‘ํ•˜๋Š” Web Speech API์˜ ์Œ์„ฑ์ธ์‹ ์‹ ๋ขฐ๋„(ํ•œ๊ตญ์–ด, 92%)์„ ํ™•์ธ ํ›„ ํ•ด๋‹น API ์‚ฌ์šฉ์— ์ ‘๊ทผ

CSR = Naver CLOVA Speech Recognition, K_TTS = Kakao Speech-to-Text system, WSA = Web Speech API

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2021-11-25 แ„‹แ…ฉแ„’แ…ฎ 12.48.59

Web Speech API ์„ ์ •

  • ํ•œ๊ตญ์–ด ์‹ ๋ขฐ๋„ ์ˆœ์€ CSR > K_TTS > Web Speech API

  • ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ๋กœ์ง์ˆœ์„œ๋Š”

    • CSR, K_TTS์˜ ๊ฒฝ์šฐ,
      • Template(์‚ฌ์šฉ์ž ์Œ์„ฑ ๋…น์Œ) > Axios(JS, ํŒŒ์ผ๋กœ ๋ณ€ํ™˜ ํ›„ ์ „์†ก) > CSR, K_TTS(๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌํ›„ Response) > Axios(๋ฐ์ดํ„ฐ ์ˆ˜์‹ ) > Django View(๋ฐ์ดํ„ฐ ์ „์†ก) > Django View(๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ›„ Response) > Axios(์ž‘์—… ํ›„ Template์ฒ˜๋ฆฌ)
    • WSA์˜ ๊ฒฝ์šฐ,
      • ์‚ฌ์šฉ์ž ์Œ์„ฑ ๋…น์Œ(Template) > WSA(๋ฐ”๋กœ ์ฒ˜๋ฆฌ ํ›„ ํ…์ŠคํŠธ ์ถ”์ถœ ํ›„ ์ „์†ก) > Django View(๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ›„ Response) > Axos(์ž‘์—… ํ›„ Template์ฒ˜๋ฆฌ)

    ์•ˆ ๊ทธ๋ž˜๋„ ๋งŽ์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์—…๋กœ๋“œ ๋˜์–ด์•ผํ•˜๊ณ , ํ†ต์‹ ๋„ ๋งŽ์ด ํ•˜๋Š” ์‚ฌ์ดํŠธ์—์„œ CSR, K_TTS ๋ณด๋‹ค๋Š” WSA๊ฐ€ ํ›จ์”ฌ ๊ฐ€๋ฒผ์šธ ๊ฒƒ์ด๋ผ ์˜ˆ์ธก

  • ๊ตฌํ˜„ ์‚ฌ์ดํŠธ์—์„œ ํ•„์š”ํ•œ ๊ฒƒ์€ ์–ด๋Š์ •๋„์˜ ์ธ์‹๋ฅ  ์ด์ƒ

    ์Œ์„ฑ์ธ์‹ ์ฒ˜๋ฆฌ๊ฐ€ ๋ชฉ์ ์ธ ์‚ฌ์ดํŠธ๊ฐ€ ์•„๋‹ˆ๋ฉฐ, ์ฒ˜๋ฆฌํ•ด์•ผํ•˜๋Š” ์š”์ฒญ์ด ๋งŽ์ง€ ์•Š์•˜๋‹ค. ๋”ฐ๋ผ์„œ WSA์˜ ์‹ ๋ขฐ๋„ 92%๋Š” ์ถฉ๋ถ„ํ•˜๋‹ค๊ณ  ํŒ๋‹จ

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2021-11-25 แ„‹แ…ฉแ„’แ…ฎ 12.58.12

HTML - Modal ์ด์šฉ, ํŒ์—… ํ˜ธ์ถœ

๋…น์Œ ์•„์ด์ฝ˜ / ์ •์ง€ ์•„์ด์ฝ˜ ๊ธฐ์ค€์œผ๋กœ ๋…น์Œ ์‹œ์ž‘ / ์ •์ง€ ๋ฐ ๋กœ์ง ์‹œ์ž‘์„ Flag ์ฒ˜๋ฆฌ

์ฒ˜๋ฆฌ๋งˆ๋‹ค ์•„์ด์ฝ˜ ๋ฐ Answer Text ๋ณ€๊ฒฝ

<div>
  <h4 id="voiceText"> ์Œ์„ฑ์„ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ์–ด์š”...</h4>
  <h4 class="d-none" id="voiceAnswer" > ์•Œ๊ฒ ์–ด์š”, ์ง€๊ธˆ ๋ฐ”๋กœ ์‹คํ–‰ํ• ๊ฒŒ์š”.</h4>
</div>

<div class="modal-footer">
  <button id="voiceBtn" type="button" class="btn btn-danger">
    <i class="fas fa-microphone-alt"></i>
  </button>
</div>

WebRecognitionAPI & Axios & Python

  • CDN - Webkit ์•ˆ์— ๋‚ด์žฅ

  • Instance - new webkitSpeechRecognition

  • ํ…์ŠคํŠธ ์ถ”์ถœ - Instance 'result' ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ, event['results']์˜ index 0

  • ํ…์ŠคํŠธ ์ถ”์ถœ ํ›„ ๋กœ์ง

    • voice_process๋กœ Axios ์š”์ฒญ
    • vocie_process ์ฒ˜๋ฆฌ ํ›„ Response
    • Axios์—์„œ js๋กœ template ์กฐ์ •
Voice Recognition Script
{% comment %} ์Œ์„ฑ์ธ์‹ {% endcomment %}
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>

  <script>
    // Required Definition
    const voiceBtn = document.querySelector('#voiceBtn')
    const voiceText = document.querySelector('#voiceText')
    const processURL = '/movies/voice_process/'
    const voiceAnswer = document.querySelector('#voiceAnswer')

    let speech = new webkitSpeechRecognition // Voice Recording Tool
    let flag = true // start|stop flag

    voiceBtn.addEventListener('click', function () {
      if (flag) {
        speech.start()
        voiceText.innerText = "๋“ฃ๊ณ  ์žˆ์–ด์š”..."
        voiceBtn.innerHTML = '<i class="fas fa-stop" style="font-size:35px;"></i>'
        voiceAnswer.classList.add('d-none')
        flag = false
      } else {
        speech.stop()
        voiceBtn.innerHTML = '<i class="fas fa-microphone-alt" style="font-size:35px;"></i>'
        flag = true
        
        // Connecting to voice_process in View
        axios({
          method: 'GET',
          url: processURL,
          params: {
            data: voiceText.textContent,
          }
        })
          .then(function (response) {
            const res = response.data.res // Number for Recognition
            if (res != 0 && res <= 3) {
                // href to Main by Sorting
              voiceAnswer.classList.remove('d-none')
              voiceAnswer.innerText = "์•Œ๊ฒ ์–ด์š”, ๋ฐ”๋กœ ์‹คํ–‰ํ• ๊ฒŒ์š”."
              setTimeout(function() {
                window.location.href = `/movies/index/${res}`
              }, 1000);  
            } else if (res >= 4) {
              if (res == 4) {
                // Search
                let query = response.data.query
                const searchInput = document.querySelector('#searchInput')
                const searchButton = document.querySelector('#searchButton')
                searchInput.value = query
                voiceAnswer.classList.remove('d-none')
                voiceAnswer.innerText = "์•Œ๊ฒ ์–ด์š”, ๋ฐ”๋กœ ์‹คํ–‰ํ• ๊ฒŒ์š”."
                setTimeout(function() {
                  searchButton.click()
                }, 1000);
              } else if (res == 5) {
                // Click BackBtn
                voiceAnswer.classList.remove('d-none')
                voiceAnswer.innerText = "์•Œ๊ฒ ์–ด์š”, ๋ฐ”๋กœ ์‹คํ–‰ํ• ๊ฒŒ์š”."
                setTimeout(function() {
                  let backTag = document.querySelector('#backTag')
                  backTag.click()
                }, 1000);
              } else if (res == 6) {
                // About UserInfo Update
                voiceAnswer.classList.remove('d-none')
                voiceAnswer.innerText = "์•Œ๊ฒ ์–ด์š”, ๋ฐ”๋กœ ์‹คํ–‰ํ• ๊ฒŒ์š”."
                setTimeout(function() {
                window.location.href = '/accounts/logout'
              }, 1000);
              }
            } else {
              voiceAnswer.classList.remove('d-none')
              voiceAnswer.innerText = "ํ•ด๋‹น ์„œ๋น„์Šค๋Š” ์ค€๋น„์ค‘์ž…๋‹ˆ๋‹ค."
            }
          })
        }})
    
    // Recording Response Tracking
    speech.addEventListener("result", function (event) {
      const { transcript } = event["results"][0][0]
      voiceText.innerText = `"${transcript}"`
    })
  </script>

voice_process
# movies/views.py

def voice_process(request):
    data = request.GET['data']
    responsable = {"๋ฉ”์ธ": 1, "๋งค์ธ": 1, "์ƒ‰์ฒด": 1, "์ƒ‰์ฑ„": 1, "ํ‰์ ": 2, "์ตœ์‹ ": 3, "๊ฒ€์ƒ‰": 4, "๋’ค๋กœ":5, "๋กœ๊ทธ": 6}
    res, query = 0, ""
    
    for key, value in responsable.items():
        if key in data:
            if key == "๊ฒ€์ƒ‰":
                query = str(data[1:data.index("๊ฒ€์ƒ‰")])
            res = value
            break
        
    context = {
        'res' : res,
        'query' : query,
    }
    return JsonResponse(context)

๋“œ๋กœ์ž‰

์‚ฌ์šฉ์ž ๋กœ์ปฌ์— ์ €์žฅํ•˜์ง€ ์•Š๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”๋กœ ์ „์†กํ•˜๊ธฐ ์œ„ํ•ด Blob ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ํ›„, FormData ์ƒ์„ฑ ํ›„ Views.py ์ฒ˜๋ฆฌ, Json Response, Axios Promise ๋™์ž‘

๊ทธ๋ฆผํŒ
  • https://github.com/shlee0882/painting-js ์ด์šฉ ๋ฐ ์‘์šฉ
  • ์‚ฌ์šฉ์ž๊ฐ€ ๊ทธ๋ฆด ์ˆ˜ ์žˆ๋Š” ์ปฌ๋Ÿฌ๋Š” ์˜ํ™”์˜ ์ปฌ๋Ÿฌ ์ƒ‰์ƒ์œผ๋กœ ํ•œ์ •

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2021-11-25 แ„‹แ…ฉแ„’แ…ฎ 1.19.55

HTML - Modal ์ด์šฉ, ํŒ์—… ํ˜ธ์ถœ

์Šค์ผ€์น˜๋ถ ์—ด๊ธฐ ๋ฒ„ํŠผ์„ ํ†ตํ•ด ๊ทธ๋ฆผํŒ display ํ† ๊ธ€

ํ•ด๋‹น flag๋กœ ๊ทธ๋ฆผ ๋ฐ์ดํ„ฐ ์—†์„ ๊ฒฝ์šฐ ๊ทธ๋ฆผ์ด ์—†๋Š” ๊ฒƒ์œผ๋กœ ์ฒ˜๋ฆฌ

<div class="content-section mt-3">
  <div class="app-card mt-5">
    <button style="background: none; border:none;" id="drawBtn">
      <h1 style="color:white;">์Šค์ผ€์น˜๋ถ ์—ด๊ธฐ</h1>
    </button>
  </div>
</div>

<div class="content-section d-none" id="drawWindow">
  <div class="app-card mt-3">
    <div class="row d-flex justify-content-center align-items-center">
      <canvas id="drawCanvas" class="canvas col-6" style="opacity: 0.8;"></canvas>
      <div class="controls col-5">
        <div class="controls__range">
          <input type="range" id="jsRange" min="0.1" max="10.0" value="5.0" step="0.1" style="width:200"/>
        </div>
        <!-- div.controls__btns>button#jsMode+button#jsSave -->
        <div class="controls__btns">
          <button id="jsMode" style="opacity: 0.8;">ํŽœ</button>
        </div>
        <div class="controls__colors" id="jsColors">
          {% for color in movie.color_set.all %}
          <div class="controls__color jsColor" style="background-color: rgb({{color.color_1_R}},{{color.color_1_G}},{{color.color_1_B}});"></div>
          <div class="controls__color jsColor" style="background-color: rgb({{color.color_2_R}},{{color.color_2_G}},{{color.color_2_B}});"></div>
          <div class="controls__color jsColor" style="background-color: rgb({{color.color_3_R}},{{color.color_3_G}},{{color.color_3_B}});"></div>
          <div class="controls__color jsColor" style="background-color: rgb({{color.color_4_R}},{{color.color_4_G}},{{color.color_4_B}});"></div>
          <div class="controls__color jsColor" style="background-color: rgb({{color.color_5_R}},{{color.color_5_G}},{{color.color_5_B}});"></div>
          {% endfor %}
        </div>
      </div>
    </div>
  </div>
</div>
Draw Script
  • Reference - https://github.com/shlee0882/painting-js)
  • Reference ๊ทธ๋Œ€๋กœ ์ด์šฉ, ๊ทธ๋ฆผํŒ์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด ๋ชฉํ‘œ๊ฐ€ ์•„๋‹ˆ์˜€์œผ๋ฏ€๋กœ ์˜คํ”ˆ์†Œ์Šค CSS ํ™œ์šฉ๊ณผ ๊ฐ™๋‹ค๊ณ  ํŒ๋‹จ
Blob ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ & FormData ์ƒ์„ฑ & Axios
  • Blob ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ๋ฐ FormData ์ƒ์„ฑ ์ด์œ 
    • ์‚ฌ์šฉ์ž ๋กœ์ปฌ์— ์ €์žฅํ•˜์ง€ ์•Š๊ณ  ํŒŒ์ผ์„ ๋ฏธ๋””์–ด ํŒŒ์ผ์— ์—…๋กœ๋“œ >> Blob ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ํ›„ ๋ฐ์ดํ„ฐ PUSH
    • input์— ๋ฐ์ดํ„ฐ ๊ฐ’์„ JS๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋Š” ์ •์ฑ… >> ์ƒˆ๋กœ์šด FormData ์ƒ์„ฑํ›„ ํ•ด๋‹น ๋ฐ์ดํ„ฐ POST
function submitFunc(){
  event.preventDefault()
  let drawCanvas = document.querySelector('#drawCanvas')
	
  data = new FormData()                                                           	// FormData ์ƒ์„ฑ
	

  var imgDataUrl = drawCanvas.toDataURL('image/png');
  var binaryData = atob(imgDataUrl.split(',')[1]);                           	 	   // Blob ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜
  var array = [];
  for (var i = 0; i < binaryData.length; i++) {
    array.push(binaryData.charCodeAt(i));
  }
  var blob = new Blob([new Uint8Array(array)], {type: 'image/png'});


  const title = document.querySelector('#title')
  const content = document.querySelector('#content')
  const moviePk = "{{movie.pk}}"
  if (drawBtnFlag) {
  } else {
    data.set('draw', blob, 'my_draw.png')
  }
  data.set('title', title.value)
  data.set('content', content.value)
	
  axios({                                                           								// Axios
    url: '',
    method: 'POST',
    data: data,
    headers: {"X-CSRFToken": csrfToken, "Content-Type": "multipart/form-data" },
  })
    .then(response => {
      window.location.href = `/movies/detail/${moviePk}/`
    })
    .catch(err => {
      alert("์ž‘์„ฑ ๊ฐ’์— ์ด์ƒ์ด ์žˆ์–ด์š”")
    })
}

review_create / quiz_create
@login_required
@require_http_methods(['GET','POST'])
def review_create(request, movie_pk):
    movie = Movie.objects.get(pk = movie_pk)
    
    # Profile Color Create
    colors = request.user.usercolorrecord_set.all()                # Color List
    last_color = colors[len(colors)-1]                             # Picked Color
    colors = reversed(colors)                                      # Color Sort(Recently)
    
    if request.method == "POST":
        form = ReviewForm(request.POST, request.FILES)
        if form.is_valid():
            review = form.save(commit=False)
            review.movie = movie
            review.user = request.user
            review.save()
            request.user.point += 30
            request.user.save()
            return redirect('movies:detail', movie_pk)
    else:
        form = ReviewForm()
    context = {
        'movie': movie,
        'form': form,
        'last_color': last_color,
        'colors': colors,
    }
    return render(request, 'movies/review_create.html', context)
  
@login_required
@require_http_methods(['GET','POST'])
def quiz_create(request):
    # Profile Color Create
    colors = request.user.usercolorrecord_set.all()                # Color List
    last_color = colors[len(colors)-1]                             # Picked Color
    colors = reversed(colors)                                      # Color Sort(Recently)
    
    movies = Movie.objects.all()
    
    if request.method == "POST":
        form = QuizForm(request.POST, request.FILES)
        if form.is_valid():
            quiz = form.save(commit=False)
            quiz.user = request.user
            quiz.save()
            request.user.point += 30
            request.user.save()
            return redirect('movies:index', 2)
    else:
        form = QuizForm()
    context = {
        'form': form,
        'last_color': last_color,
        'colors': colors,
        'movies': movies,
    }
    return render(request, 'movies/quiz_create.html', context)

Quiz

ํ€ด์ฆˆ ํ’€์ด ํ™”๋ฉด
  • ์‚ฌ์šฉ์ž๋ณ„๋กœ ๋งž์ถ˜ ๋ฌธ์ œ๋Š” ๋ณด์ด์ง€ ์•Š์Œ

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2021-11-25 แ„‹แ…ฉแ„’แ…ฎ 2.31.29

  • Axios์™€ quiz_check(views.py) ์ฒ˜๋ฆฌ

๋ฌธ์ œ์˜ pk์™€ ์ œ์ถœ๋œ value pk ๋ฅผ ๋น„๊ต

  • JS Code
{% block option_js %}
<script>
  const forms = document.querySelectorAll("#answerForm")
  const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value

  for(const form of forms){
    form.addEventListener("submit", function (event) {
      event.preventDefault()
      const questionPk = event.target.dataset.questionPk
      const answer = event.submitter.value

      axios({
        method: 'post',
        url: '/movies/quiz_check/',
        headers: {'X-CSRFToken': csrfToken},
        data: {
          test: "Test",
          question_pk: `${questionPk}`,
          answer: `${answer}`,
        }
      })
        .then(function (res) {
          const correct = res.data.correct
          const main = document.querySelector(`#main${questionPk}`)
          const right = document.querySelector(`#right${questionPk}`)
          const wrong = document.querySelector(`#wrong${questionPk}`)

          if (correct) {
            main.classList.add('d-none')
            right.classList.remove('d-none')
          } else {
            main.classList.add('d-none')
            wrong.classList.remove('d-none')
          }
        })
        .catch(function (err) {
          console.log(err)
        })
    })
  } 
</script>
{% endblock option_js %}
  • quiz_check.py
@login_required
@require_http_methods(['GET','POST'])
def quiz_check(request):
    post_body = json.loads(request.body.decode('utf-8'))
    
    question_pk = post_body.get('question_pk')
    answer = post_body.get('answer')

    quiz = Quiz.objects.get(pk=question_pk)
    correct = False

    if int(quiz.movie.pk) == int(answer):
        print("์ •๋‹ต")
        correct = True
        
        request.user.point += 20
        quiz.correct_user.add(request.user)
        request.user.save()

    context = {
        "correct": correct,
    }

    return JsonResponse(context)
ํ€ด์ฆˆ ๋งŒ๋“ค๊ธฐ ํ™”๋ฉด
  • ์˜ํ™” ์„ ํƒ ํ›„, ๋ฌธ์ œ๋ฅผ ๊ทธ๋ฆผํŒ์œผ๋กœ ๊ทธ๋ฆฌ๊ณ  ๋ฐ”๋กœ ์ถœ์ œ ๊ฐ€๋Šฅ

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2021-11-25 แ„‹แ…ฉแ„’แ…ฎ 2.31.43

  • ์ฝ”๋“œ - ์ƒ๋‹จ ๋“œ๋กœ์ž‰ ์ฐธ์กฐ
ํ€ด์ฆˆ ๋ฒ„ํŠผ ์ œ์ถœ์‹œ(๋น„๋™๊ธฐ ์—…๋ฐ์ดํŠธ)
  • ๋งž์ถ”๊ณ  ํ‹€๋ฆฐ ๊ฒƒ์— ๋”ฐ๋ผ ํ•ด๋‹น ๋ฌธ์ œ๊ฐ€ ์ฒดํฌ๋˜๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ ์Šคํฌ๋กค์„ ๋‚ด๋ฆด ์ˆ˜ ์žˆ๋„๋ก UI ๊ตฌ์„ฑ
  • classList.add / remove d-none + promise ๋™์ž‘๋ฐฉ์‹์œผ๋กœ ๊ตฌ์„ฑ

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2021-11-25 แ„‹แ…ฉแ„’แ…ฎ 2.32.42

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2021-11-25 แ„‹แ…ฉแ„’แ…ฎ 2.31.54

DB

ColorData, TMDB API(์˜ํ™” ์ •๋ณด), Youtube Data(trailer) ๋“ฑ์ด ๋“ค์–ด๊ฐ์— ๋”ฐ๋ผ ์ƒˆ๋กœ์šด DB๋ฅผ ๋งŒ๋“ค ํ•„์š”

DB Modeling
  • ๊ธฐ๋Šฅ๊ณผ ๋“ค์–ด๊ฐˆ ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์€ ๋งŒํผ ๊ทœ๋ชจ๊ฐ€ ์žˆ๋Š” DataBase ์ƒ์„ฑ
  • BASE ๋ฐ์ดํ„ฐ์ธ ์˜ํ™”๋ฐ์ดํ„ฐ๋Š” ์ง์ ‘ ์กฐํ•ฉํ•ด์„œ PUSH ํ•ด์•ผํ•  ํ•„์š”
    • ํด๋Ÿฌ์Šคํ„ฐ๋ง ๋ฐ RGB ๋„์ถœ๋œ ๋ฐ์ดํ„ฐ
    • TMDB API ์˜ํ™”์ •๋ณด
    • Youtube Trailer URL ๋“ฑ

erd

Frame ์ด๋ฏธ์ง€ PUSH

color_url_push

Color Model Database PUSH
  • ๋งŒ๋“ค์–ด์ง„ json ๋ฐ์ดํ„ฐ๋ฅผ Movie๋ฅผ ์ •์ฐธ์กฐ ํ•˜๊ณ  ์žˆ๋Š” Color ๋ชจ๋ธ์— PUSH

  • ํŠน์ •์ƒ 5๊ฐœ RGB ๋ฐ์ดํ„ฐ PUSH

  • ์š”์†Œ๊ฐ€ ๋งŽ์œผ๋ฏ€๋กœ ์ž‘์—… ์ „ Python ์ด์šฉ

  • datastringmake

makecolorsdata

  • Result

res

TMDB API ํ˜ธ์ถœ ๋ฐ ํ•ด๋‹น ๋ฐ์ดํ„ฐ PUSH
  • Response ๋ฐ์ดํ„ฐ

  • json

for

๊ธฐ์กด์— Naver ํ‰์ ์„ API ํ˜ธ์ถœํ•˜๋ ค ํ–ˆ์œผ๋‚˜, ์˜ํ™”์ œ๋ชฉ ํฌํ•จ์—ฌ๋ถ€๋กœ response๋ฅผ ์ฃผ์–ด์„œ TMDB ํ‰์ ์œผ๋กœ ๋ณ€๊ฒฝ
  • Naver

naver

  • TMDB

requests

  • Result

res2

result2

Youtube Trailer Link PUSH
  • Iframe ์˜ˆ๊ณ ํŽธ ์ž‘์—… ์ „ link PUSH

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2021-11-23 แ„‹แ…ฉแ„’แ…ฎ 7.11.11

Query ์ตœ์ ํ™”

๋ณต์žกํ•œ ๋ชจ๋ธ๊ตฌ์กฐ๋กœ ์—ญ์ฐธ์กฐ, ์ •์ฐธ์กฐ ๋ถ€๋ถ„์—์„œ ์ค‘๋ณต Query ๋ฐœ์ƒ

Query ์ตœ์ ํ™” ์ž‘์—… ์ „ Index Page
  • 408 Query / Time 7000ms ~ 9000ms

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2021-11-22 แ„‹แ…ฉแ„Œแ…ฅแ†ซ 10.25.55

Query ์ตœ์ ํ™” ์ž‘์—… ํ›„ Index Page
  • 9 Query / Time 250ms ~ 300ms (์•ฝ Query 1/40, Time 1/30 ์œผ๋กœ ๊ฒฝ๋Ÿ‰ํ™”)

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2021-11-22 แ„‹แ…ฉแ„Œแ…ฅแ†ซ 11.44.30

์‚ฌ์šฉ Stack
  • prefetched_realated - (์—ญ์ฐธ์กฐ ๋ฐ์ดํ„ฐ ์‚ฌ์ „ ๋“ฑ๋ก)
  • annotate - (count ์‚ฌ์ „ ๋“ฑ๋ก)
  • filter, exclude ๋“ฑ - ๊ณ„์‚ฐ ์ตœ์ ํ™”
# index/views.py

    # Exclude Already Watched Movies(Query Optimization)
    already_watched_movies = set()

    users_moviecomments = MovieComment.objects.filter(user=request.user.pk).values('movie')
    for users_moviecomment in users_moviecomments:
        already_watched_movies.add(users_moviecomment['movie'])

    users_reviews = Review.objects.filter(user=request.user.pk).values('movie')
    for users_review in users_reviews:
        already_watched_movies.add(users_review['movie'])

    # Query Optimization - movie.color_set + Exclude Already Watched Movies + movie.comments_set + movie.comments.all.count()
    movies = Movie.objects.prefetch_related('color_set','comments')\
            .annotate(comment_count=Count('comments'))\
            .exclude(id__in=already_watched_movies)
    
    res_movies = []
    
    # Order by TMDB_Grade + User_Grade
    if mode == 2:
        for movie in movies:
            naver_score = movie.naver_grade
            user_score = 0 
            for comment in movie.comments.all():
                user_score += comment.grade
            if user_score:
                user_score = round((user_score / movie.comment_count),1) * 2
                score = (naver_score + user_score) / 2
            else:
                score = naver_score
            res_movies.append([movie, score])
    
    # Order by Release Date
    elif mode == 3:
        for movie in movies:
            score = movie.release_date
            res_movies.append([movie, score])
    
    # Order by Search
    elif mode == 4:
        datas = request.GET['searchData'].split()                      # Input Data list
        for movie in movies:                                           # query in title : +10, query in overview +1
            temp_cnt = 0
            title = movie.title
            overview = movie.overview
            for data in datas:
                if data in title:
                    temp_cnt += 10
                if data in overview:
                    temp_cnt += 1
            if temp_cnt >= 1:                                          # If query in title, content, Can be Searched
                res_movies.append([movie,temp_cnt])

๋ฐฐํฌ

AWS EC2(Ubuntu Server 18.04 LTS), Cloud9, Gunicorn, NGINX

Django contains STATIC / MEDIA

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2021-11-25 แ„‹แ…ฉแ„’แ…ฎ 3.29.14

NGINX ์„ค์ •
  • static๊ณผ media files๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์–ด NGINX์—์„œ ์ ‘๊ทผํ•˜์ง€ ๋ชปํ•˜๋Š” ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒ(404)
  • static์€ ๊ฒฝ๋กœ๋ฅผ staticfiles/static์œผ๋กœ ์žก์•„์ค˜์„œ staticfiles/static ๊ฒฝ๋กœ๋ฅผ ๋งŒ๋“ค์–ด ์คŒ์œผ๋กœ์จ ํ•ด๊ฒฐ
  • media ํŒŒ์ผ ์ ‘๊ทผ์˜ ๊ฒฝ์šฐ์—๋Š” static ์ฒ˜๋Ÿผ root๋กœ ์žก์•„์ฃผ๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ
    • alias๋กœ ๋ฐ”๊ฟ” mediaํŒŒ์ผ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ์ฒ˜๋ฆฌ
   server_name 18.214.88.219 prismfilmstudio.com www.prismfilmstudio.com;

    location ^~ /static/ {
            root /home/ubuntu/pjt10/staticfiles/;
    }

    location ^~ /media/ {
            alias /home/ubuntu/pjt10/media/;
    }

    location / {
            include proxy_params;
            proxy_pass http://127.0.0.1:8000;
    }

listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/prismfilmstudio.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/prismfilmstudio.com/privkey.pem; # managed by Certbot
Django & Gunicorn ์„ค์ •
  • Django - STATIC ROOT, ALLOWED_HOSTS, collectstatic ๋ช…๋ น
  • Gunicorn ์„ค์ •
[Unit]
Description=gunicorn daemon
After=network.target

[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/pjt10
ExecStart=/home/ubuntu/pjt10/venv/bin/gunicorn \
        --workers 3 \
        --bind 127.0.0.1:8000 \
        pjt10.wsgi:application

[Install]
WantedBy=multi-user.target
๋„๋ฉ”์ธ ์„ค์ • & ํฌํŠธ ์„ค์ • & HTTPS
  • www.~ ์œผ๋กœ๋„ ๋“ค์–ด์˜ฌ ์ˆ˜ ์žˆ๋„๋ก A type ํ•˜๋‚˜ ๋” ๊ฐœ๋ฐฉ

    แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2021-11-25 แ„‹แ…ฉแ„’แ…ฎ 3.40.07

  • ํ•ด๋‹น ์„ค์ •์— ๋”ฐ๋ฅธ NGINX ์„ค์ •

  • HTTPS ์„ค์ •

sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot --nginx
  • ํฌํŠธ์„ค์ •

  • 80 / 8000 / 443(https) => 0.0.0.0/0 / ::/0 ๊ฐœ๋ฐฉ

    แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2021-11-25 แ„‹แ…ฉแ„’แ…ฎ 3.44.55

๐Ÿ“– Reference

formdata - https://developer.mozilla.org/ko/docs/Web/API/FormData

๊ทธ๋ฆผํŒ - https://github.com/shlee0882/painting-js

blob Data ๋ณ€ํ™˜ - https://codebb.tistory.com/22

๋ฐฐ๊ฒฝ ํ…Œ๋งˆ - https://codepen.io/trending

Axios - https://github.com/axios/axios

Base - https://edu.ssafy.com

์ปฌ๋Ÿฌ๋ฐ”์ฝ”๋“œ - https://happycoding.io/gallery/movie-colors/index

๋Œ€ํ‘œ ์ƒ‰์ฑ„ - https://airows.com/culture/color-palettes-from-famous-movie-scenes

ํ‹ฐ์–ด ์‹œ์Šคํ…œ - https://www.acmicpc.net/, https://solved.ac/

๐Ÿ“œ ๋งˆ์น˜๋ฉฐ

๊น€์ฃผํ˜ธ

์•„์ด๋””์–ด ํšŒ์˜์— ์ƒ๋‹นํ•œ ๊ณต์„ ๋“ค์˜€๋‹ค. ์•„์ด๋””์–ด๊ฐ€ ๋– ์˜ค๋ฅด์ง€ ์•Š์œผ๋ฉด ์ž‘์—… ์ฐฉ์ˆ˜ํ•˜๊ธฐ ํž˜๋“  ํƒ€์ž…์ธ๋ฐ, ๋‹คํ–‰ํžˆ ํŽ˜์–ด์˜€๋˜ ๊ฑดํฌ๋‹˜๊ป˜์„œ ์•„์ด๋””์–ด๋ฅผ ์†์‰ฝ๊ฒŒ ๋„์ถœํ•  ์ˆ˜ ์žˆ๋„๋ก ํŽธํ•œ ๋ถ„์œ„๊ธฐ๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ์…”์„œ ์ข‹์€ ์•„์ด๋””์–ด๋“ค์ด ๋งŽ์ด ๋‚˜์˜ฌ ์ˆ˜ ์žˆ์—ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

์ดˆ๋ฐ˜ ๋ฐ์ดํ„ฐ์ž‘์—…๊ณผ C# ์ฝ”๋“œ๋ฅผ ๋ณ‘ํ–‰ํ•˜๋Š๋ผ ์›นํŽ˜์ด์ง€ ์ชฝ์—๋Š” ์‹ ๊ฒฝ์„ ๋งŽ์ด ๋ชป ์ผ์—ˆ๋Š”๋ฐ, ์ž‘์—… ์ข…๋ฃŒ ํ›„ ์‚ดํŽด๋ณด๋‹ˆ ๊ฑดํฌ๋‹˜๊ป˜์„œ ๋Œ€๋ถ€๋ถ„์˜ ๊ตฌ์กฐ์™€ ํ‹€์„ ์žก์•„ ๋†“์€ ์ƒํƒœ๋ผ ๋†€๋ผ์› ๊ณ , ๊ฐ์‚ฌํ–ˆ๊ณ , ํ•œํŽธ์œผ๋กœ๋Š” ์ •๋ง ์ฃ„์†ก์Šค๋Ÿฌ์› ๋‹ค. ์˜ค๋žœ๋งŒ์— C#์„ ๋‹ค๋ค˜๋˜์ง€๋ผ ์ฝ”๋“œ๊ฐ€ ์ž๊พธ ์ œ๋ฉ‹๋Œ€๋กœ ๊ตด๋Ÿฌ๊ฐ€์„œ ์ˆ˜์ •ํ•˜๋Š” ์‹œ๊ฐ„๋„ ๊ฝค ๊ฑธ๋ ธ๊ณ , ์˜ํ™” ์˜ˆ๊ณ ํŽธ ์ €์žฅ ๋ฐ ๋ฐ์ดํ„ฐ ์ถ”์ถœ๊นŒ์ง€ ํ•ด์„œ ๋Œ€๋žต 2~3์ผ์ด ๊ฑธ๋ ธ๋Š”๋ฐ ๊ทธ ์•ˆ์— ์–ด๋–ป๊ฒŒ ๊ทธ๋ ‡๊ฒŒ ๋‹จ๋‹จํ•œ ํ‹€์„ ๋งŒ๋“ค์–ด ๋‘์…จ์„๊นŒ?
๊ฐ€๋งŒํžˆ ์žˆ์„ ์ˆ˜๋งŒ์€ ์—†์–ด์„œ ๋‚ด๊ฐ€ ํ•  ์ˆ˜ ์žˆ๋Š”๊ฑธ ์ตœ๋Œ€ํ•œ ํ•˜๋ ค๊ณ  ํ–ˆ๋‹ค. ์•ž์„œ ๋งŒ๋“  ๋ฐ์ดํ„ฐ๋“ค์„ ์ •๋ˆํ•˜๊ณ , ์›นํŽ˜์ด์ง€์—์„œ ์•ฝ๊ฐ„์˜ ๋””ํ…Œ์ผ๋“ค์„ ์ถ”๊ฐ€์‹œํ‚ค๋ฉฐ '์–ด๋–ป๊ฒŒ ํ•ด์•ผ ๋” ์œ ์ €์—๊ฒŒ ๊น”๋”ํ•œ ์ธ์ƒ์„ ์ค„ ์ˆ˜ ์žˆ์„๊นŒ?'๋ฅผ ๊ณ ๋ฏผํ–ˆ๋‹ค.

์น˜์•„ ๊ต์ •์œผ๋กœ ๋ณธ๊ฐ€์— ์ž ์‹œ ๋‹ค๋…€์˜จ ์‚ฌ์ด์—, ํ™ˆํŽ˜์ด์ง€๋ฅผ ๋” ํŽธํ•˜๊ณ  ๊ธฐ๋Šฅ์ ์ด๊ฒŒ ๊ตฌํ˜„ํ•œ ๊ฑดํฌ๋‹˜์„ ๋ต™๊ณ ์„  ์—ฌ๋Ÿฌ ๋งŒ๊ฐ์ด ๊ต์ฐจํ–ˆ๋‹ค. ๋‚œ ์ •๋ง ์—ด์‹ฌํžˆ ํ•˜๊ณ  ์žˆ๋Š”๊ฐ€? ๋‚˜ ๋•Œ๋ฌธ์— ๊ฑดํฌ๋‹˜์˜ ์•„์ด๋””์–ด๊ฐ€ ๊ตฌํ˜„๋˜์ง€ ๋ชป ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ธฐ์ง€ ์•Š์„๊นŒ?
์ž์ทจ๋ฐฉ์— ์•‰์•„ ์ปดํ“จํ„ฐ๋ฅผ ์ผœ๋ฉฐ ๊ณจ๋˜˜ํžˆ ์ƒ๊ฐํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ ์„  '๋‚ด๊ฐ€ ๋ถ€์กฑํ•จ์ด ์žˆ๋‹ค ํ•œ๋“ค, ๊ฑดํฌ๋‹˜์˜ ๋ฐœ๋ชฉ์„ ๋ถ™์žก๋Š” ๋ชจ์–‘์ƒˆ๋Š” ๋˜์ง€ ๋ง์•„์•ผ๊ฒ ๋‹ค'๊ณ  ๋‹ค์งํ–ˆ๋‹ค.

์šฐ๋ฆฌ๋Š” ์—ด์ •์ ์œผ๋กœ ์‰ฌ๋Š” ์‹œ๊ฐ„๋„ ์ค„์—ฌ๊ฐ€๋ฉฐ ์ž‘์—…์— ๋ชฐ๋‘ํ–ˆ๋‹ค. ๊ฑดํฌ๋‹˜์€ ๊ณ„์†ํ•ด์„œ ๋” ๊ฐ„ํŽธํ•˜๊ณ  ์งœ์ž„์ƒˆ์žˆ๊ฒŒ ์›นํŽ˜์ด์ง€๋ฅผ ๊ตฌ์ถ•ํ•ด ๋‚˜์•„๊ฐ”์œผ๋ฉฐ, ๋‚˜๋„ ๊ทธ ํŽ˜์ด์Šค์— ๋งž์ถฐ ์—ด์‹ฌํžˆ ๋จธ๋ฆฌ๋ฅผ ๊ตด๋ฆฌ๊ณ  ์†์„ ์›€์ง์˜€๋‹ค. ํž˜๋“ค์–ด์„œ ์ž ์‹œ ์‰ฌ๊ณ  ์‹ถ์—ˆ์„ ๋•Œ๋„ ์žˆ์—ˆ์ง€๋งŒ, ๋‚˜๋ณด๋‹ค ๋” ๊ณ ์ƒํ•˜์‹œ๋Š” ๊ฑดํฌ๋‹˜์„ ๋ณด๋ฉด์„œ ๋”์šฑ ๋” ์—ด์‹ฌํžˆ ์ฐธ์—ฌํ–ˆ๋‹ค.

๊ณ„ํšํ•œ ๋Œ€๋ถ€๋ถ„์˜ ๊ฒƒ๋“ค์„ ์ด๋ฃจ๊ณ  ๋‚˜์„œ๋„, ์šฐ๋ฆฌ๋Š” ๋” ๋งŽ์€ ๊ฒƒ๋“ค์„ ์ƒ๊ฐํ–ˆ๋‹ค. ์ฃผ๋ณ€ ์ง€์ธ๋“ค์—๊ฒŒ ์›นํŽ˜์ด์ง€์— ๋Œ€ํ•ด ๋ณด์—ฌ์คฌ๋Š”๋ฐ, ๋‹ค๋“ค ํฅ๋ฏธ๋Š” ๋Š๋‚„์ง€์–ธ์ • ํŽ˜์ด์ง€์—์„œ ๋งŽ์€ ์‹œ๊ฐ„์„ ๋ณด๋‚ด์ง„ ์•Š์•˜๊ณ , ์šฐ๋ฆฌ๋Š” ๊ทธ ๋•Œ '์‚ฌ์šฉ์ž์˜ ์ž…์žฅ์—์„œ ์ƒ๊ฐํ•˜๋ผ'๋Š” ๋ง์„ ๋– ์˜ฌ๋ ธ๋‹ค.
๊ทธ๋ž˜, ํฅ๋ฏธ๋„ ์ค‘์š”ํ•˜์ง€๋งŒ ์žฌ๋ฏธ๋„ ์ค‘์š”ํ•˜๊ตฌ๋‚˜.
์šฐ๋ฆฐ ์‚ฌ์šฉ์ž์—๊ฒŒ ์žฌ๋ฏธ๋ฅผ ์ฃผ๊ธฐ ์œ„ํ•ด ํ•˜๋ฃจ๋ฅผ ์˜จํ†ต ํˆฌ์žํ•˜์—ฌ ์ƒˆ๋กœ์šด ํŽ˜์ด์ง€๋ฅผ ๊ตฌ์ถ•ํ•˜์˜€๊ณ , ํ”ผ๊ณคํ•œ ์™€์ค‘์—๋„ ์„œ๋กœ ๋Œ€ํ™”ํ•˜๊ณ  ๋ฐฐ๋ คํ•˜๋ฉฐ ๋งˆ์นจ๋‚ด ์™„์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

ํ•ด๋‹น ํ”„๋กœ์ ํŠธ๊ฐ€ ํž˜๋“ค์ง€ ์•Š์•˜๋‹ค๋ฉด ๊ฑฐ์ง“์ด๊ฒ ์ง€๋งŒ ๊ทธ๋งŒํผ ๋ฟŒ๋“ฏํ•œ ๊ฐ์ •์„ ๋Š๊ผˆ๊ณ , ์ด๋กœ ์ธํ•ด ๋งŽ์€ ๊ฒƒ์„ ๋ฐฐ์› ๋‹ค๊ณ  ๋‹น๋‹นํžˆ ๋งํ•  ์ˆ˜ ์žˆ๋‹ค. ํŠนํžˆ๋‚˜ ๊ฑดํฌ๋‹˜์˜ ๊ณ„ํš์„ฑ๊ณผ ๊ณง์€ ์˜์ง€์— ์˜ํ–ฅ์„ ๋งŽ์ด ๋ฐ›์•„, '์–ด๋– ํ•œ ํ”„๋กœ๊ทธ๋ž˜๋จธ๊ฐ€ ๋˜์–ด์•ผ ํ•˜๋Š”๊ฐ€'๋ฅผ ๊ณ ์ฐฐํ•˜๊ฒŒ ๋œ ์†Œ์ค‘ํ•œ ์‹œ๊ฐ„์ด์—ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

์ •๋ง ๋งŽ์€ ๊ฒƒ์„ ๊นจ๋‹ซ๊ณ  ๋ฐฐ์šธ ์ˆ˜ ์žˆ๊ฒŒ๋” ํ•ด ์ฃผ์‹  ๋‚˜์˜ ํŽ˜์–ด ๊ฑดํฌ๋‹˜, ์„ฑ์žฅํ•  ๊ณ„๊ธฐ๋ฅผ ์ค€ ์‹ธํ”ผ์™€ ์กฐ์–ธ์ฃผ์‹  ๊ต์ˆ˜๋‹˜, ์‘์›ํ•ด์ค€ ๋Œ€์ „ 2๋ฐ˜ ๋™๊ธฐ๋ถ„๋“ค๊ณผ ์ฃผ๋ณ€ ์ง€์ธ๋“ค์—๊ฒŒ ์ง„์‹ฌ์œผ๋กœ ๊ฐ์‚ฌ ๋“œ๋ฆฌ๋ฉฐ ๊ธ€์„ ๋งˆ์น˜๊ฒ ๋‹ค.

์ด๊ฑดํฌ

์ฃผํ˜ธ๋‹˜๊ณผ ํ•จ๊ป˜์—ฌ์„œ ์ž˜ ๋งˆ์น  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ์ž˜ํ•˜๊ณ  ์‹ถ์€ ์š•์‹ฌ ๋•Œ๋ฌธ์— ์„œ๋‘๋ฅด๊ณ  ์กฐ๊ธ‰ํ•˜๊ฒŒ ํŽ˜์–ด๋ถ„์„ ๋Œ€ํ•œ ๊ฒƒ์„ ์•„๋‹ˆ์—ˆ์„ ์ง€ ํ›„ํšŒ๊ฐ€ ๋œ๋‹ค. ๋‹ค๋ฅธ ์‚ฌ๋žŒ๊ณผ ํ•จ๊ป˜ํ•˜๋Š” ์ฒซ ํ”„๋กœ์ ํŠธ์—ฌ์„œ ์„œํˆฐ ๋ถ€๋ถ„๋„ ๋งŽ๊ณ , ํž˜๋“  ๋ถ€๋ถ„๋„ ์žˆ์—ˆ์„ ํ…๋ฐ ๋‚ด์ƒ‰ํ•˜์ง€ ์•Š๊ณ  ๋งˆ์ง€๋ง‰๊นŒ์ง€ ๊ฐ™์ด ์ž˜ ์™€์ฃผ์‹  ์ฃผํ˜ธ๋‹˜์—๊ฒŒ ๊ฐ์‚ฌ์˜ ๋œป์„ ์ „ํ•œ๋‹ค.

์ดˆ๋ฐ˜์— ๊ฐ€์ง€๊ณ  ์žˆ์—ˆ๋˜ ์ƒ๊ฐ, '๊ฒฐ๊ณผ๊ฐ€ ์ข‹์œผ๋ฉด ์ข‹๋‹ค.'๋ผ๋Š” ์ƒ๊ฐ์ด ๊ณผ์—ฐ ๋งž๋˜ ์ƒ๊ฐ์ผ๊นŒ, ํ˜‘์—…์ด๋ผ๋Š” ํ•จ๊ป˜ ๊ฐ€๋Š” ๊ฒƒ์—์„œ ์ด ์ƒ๊ฐ์€ ํ‹€๋ฆด ์ˆ˜๋„ ์žˆ๊ฒ ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ์ด ํ‹€๋ฆฐ ์ƒ๊ฐ์„ ๊ธฐ์ €๋กœ ํ•œ ๊ฒƒ์— ๋Œ€ํ•ด์„œ ์ž˜ ํ•จ๊ป˜ ์™€์ฃผ์‹  ํŽ˜์–ด๋ถ„์— ๋Œ€ํ•œ ๊ณ ๋งˆ์›€์ด ํฐ ๊ฒƒ ๊ฐ™๋‹ค.

ํ”„๋กœ์ ํŠธ์˜ ์™„์„ฑ๋„๋Š” ๋†’๋‹ค. ์•ฝ 7์ผ, 8์ผ์ด๋ผ๋Š” ์‹œ๊ฐ„๋™์•ˆ ์ •๋ง ๋งŽ์€ ๊ฒƒ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค. ๋งŒ๋“ค๊ณ  ์‹ถ์€ ๊ฑด ๋Œ€๋ถ€๋ถ„ ๋งŒ๋“  ๊ฒƒ ๊ฐ™๋‹ค. ํ•˜์ง€๋งŒ ์ด ์ฝ”๋“œ ๊ทธ๋Œ€๋กœ ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ์šด์˜ํ•œ๋‹ค๋ฉด?, ๊ธ€์Ž„ ๋ถ€์กฑํ•œ ๋ถ€๋ถ„์ด ๊ต‰์žฅํžˆ ๋งŽ์„ ๊ฒƒ ๊ฐ™๋‹ค. ์ •๋ง ๊น”๋”ํ•˜๊ณ  ์ •์ œ๋˜๊ณ , ์ค‘๋ณต์š”์†Œ๋ฅผ ํ”ผํ•˜๊ณ , ์ปค๋ฉ˜ํŠธ๋ฅผ ํ†ตํ•œ ์„ธ์…˜๊ตฌ๋ถ„๊นŒ์ง€ ์ •์ œ๋œ ์ฝ”๋“œ๋กœ ๋งŒ๋“ค๋ ค๊ณ  ๋งŽ์ด ๊ณต๋“ค์˜€๋‹ค. ๋‹ค๋งŒ, ๋งˆ์ง€๋ง‰ ํ€ด์ฆˆ๋ถ€๋ถ„์„ ํ•˜๋ฉฐ ์กฐ๊ธ‰ํ•ด์ง€๋ฉฐ ํ•ด๋‹น ๋ถ€๋ถ„์— ๋Œ€ํ•œ ์ฝ”๋“œ ์ •์ œ ๋ฐ ๋ฏธ๋””์–ด ํŒŒ์ผ ์šฉ๋Ÿ‰ ์ฒ˜๋ฆฌ๊ฐ€ ์ด๋ฃจ์–ด ์ง€์ง€ ์•Š์•˜๋‹ค. ๊ทธ๋ž˜์„œ ๋” ์•„์‰ฌ์›€์ด ํฌ๋‹ค. ๋˜, ์•„๋ฌด๋ฆฌ ๊ณต๋“ค์˜€๋‹ค ํ•˜๋”๋ผ๋„ ํ˜„์—…์ž๊ฐ€ ๋ณด๋ฉด ๋งŽ์ด ๋ถ€์กฑํ•œ ์ฝ”๋“œ์ผ ๊ฒƒ์ด ๋ถ„๋ช…ํ•˜๋‹ค. ๋‚ด๊ฐ€ ๋ชจ๋ฅด๋Š” ๋ถ€๋ถ„, ๋ฏธ์ˆ™ํ•œ ๋ถ€๋ถ„์€ ์ง€๊ธˆ ๊ฐœ๋ฐœ ํ˜„์—…์—์„œ ๋Œ€๋ถ€๋ถ„์ผ ํ…Œ๋‹ˆ๊นŒ. ๋‹จ, ์ตœ์„ ์„ ๋‹คํ–ˆ์Œ์—๋Š” ํ‹€๋ฆผ์ด ์—†๋‹ค. ํฐ ์ผ์€ ์šฐ์—ฐํžˆ ์ด๋ฃจ์–ด์ง€์ง€ ์•Š๋“ฏ์ด, ์ž‘์€ ์ผ ํ•˜๋‚˜ํ•˜๋‚˜์— ์ตœ์„ ์„ ๋‹คํ•œ ๊ฒƒ ๊ฐ™๋‹ค.

1ํ•™๊ธฐ๋ฅผ ๋งˆ์น˜๋Š” ํ”„๋กœ์ ํŠธ์ด๊ธฐ์— 1ํ•™๊ธฐ์— ๋Œ€ํ•œ ์ƒ๊ฐ๋„ ๋งŽ๋‹ค.
์˜ค๋žœ์‹œ๊ฐ„์„ ์ •๋ง ์นœ์ ˆํ•˜๊ณ  ์ž˜ ์ง€๋„ํ•ด์ฃผ์‹  juan ๊ต์ˆ˜๋‹˜๊ณผ ํ•จ๊ป˜ ๋‹ฌ๋ ค์˜จ ์†Œ์ค‘ํ•œ ์šฐ๋ฆฌ๋ฐ˜ ๋™๊ธฐ๋ถ„๋“ค, ๋ถ€์กฑํ•œ ๋‚˜์™€ ํŽ˜์–ด๋กœ ํ•จ๊ป˜ ํ–ˆ๋˜ ์ง€์˜๋‹˜, ์ง„์„ญ๋‹˜, ๊ทธ๋ฆฌ๊ณ  ์ •๋ง ๊ฐ์‚ฌํ•œ ์ฃผํ˜ธ๋‹˜๊นŒ์ง€. SSAFY์—์„œ ๋งŽ์€ ๊ฒƒ์„ ๋ฐฐ์šฐ๊ณ  ์‹ถ์—ˆ๋‹ค. ๊ทธ ๊ฒƒ์„ ํ–ฅํ•ด ๋‹ฌ๋ ค๊ฐ€๋Š”๋ฐ ์žˆ์–ด ์ด ๋ถ„๋“ค์ด ์—†์—ˆ๋‹ค๋ฉด ๋ถˆ๊ฐ€๋Šฅํ•˜์ง€ ์•Š์•˜์„๊นŒ ์‹ถ๋‹ค.

ํ”„๋กœ์ ํŠธ๋Š” ์—ฌ๊ธฐ์„œ ๋งˆ์ณ ์‹œ์›์„ญ์„ญํ•œ ๋งˆ์Œ์ด ํฌ์ง€๋งŒ, ๊ฐœ๋ฐœ์„ ๊ณ„์† ํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ๊นŒ, ๊ทธ๊ฑธ๋กœ ์ข‹๋‹ค.