본문 바로가기
공부공부/2023 쌓여가는 나의 지식~

(FastApi & Tree view)를 이용한 디렉토리 관리 웹 페이지 구축 (2) - 디렉토리 기능 관련

by Lee_story_.. 2023. 12. 22.
728x90

 

 

해당 내용은 아래 깃허브에 정리해 두었습니다!

 

GitHub - jhyun-lee/FastApi-DirectoryGui

Contribute to jhyun-lee/FastApi-DirectoryGui development by creating an account on GitHub.

github.com

 

 

 

디렉토리 관리 웹 페이지의 마지막!

기능 부분을 진행해 보겠습니다. 

 

 

 

구현한 기능으로는 아래와 같습니다. 

 

  • 파일 업로드
  • 파일 다운로드
  • 파일 삭제
  • 디렉토리 생성
  • 디렉토리 압축/해제
  • 데이터 수 확인
  • 이미지 미리보기
  • 영상 변환 및 샘플 출력

 

위의 기능들은 대부분 python 명령어로 실행 시켜 처리하는 방식으로 구현하여 보았습니다. 

 

 

EX) 아래처럼 command를 실행시키는 함수를 만들고

ffmpeg_command = [  # 비디오에서 1프레임 샘플 전송
        'ffmpeg',
        '-i', savepath,
        '-vf', f'eq=saturation={1+saturation/100}:brightness={brightness/100}:contrast={1+contrast/100}',
        '-frames:v', '1',
        './static/sample.jpg'
    ]

    subprocess.check_output(ffmpeg_command, text=True)

 

 

그 함수를 html script에서 아래처럼 실행!

const response = await fetch(`/download_file?file_path=${encodeURIComponent(filePath)}`);

 

 

 

 

구현


- 파일 업로드 다운로드

Shutil 라이브러리를 이용하여 다운로도와 업로드를 구현해 보았습니다. 

 

#파일 업로드
@router.post("/upload_file")
async def upload_file(file: UploadFile,savepath : str):
    # 업로드된 파일 정보 출력

    upload_folder = Path(savepath)## ++ /User 1 ~ 5
    upload_folder.mkdir(parents=True, exist_ok=True)  # 업로드 폴더가 없으면 생성
    
    file_path = upload_folder / file.filename
    

    # 업로드된 파일을 서버에 저장
    with file_path.open("wb") as buffer:
        shutil.copyfileobj(file.file, buffer)



# 파일 다운로드
@router.get("/download_file")
async def download_file(file_path: str):

    file_path_on_server = Path(file_path)
    print(file_path)

    # 파일이 존재하면 해당 파일을 반환
    if file_path_on_server.exists():
        return FileResponse(file_path_on_server)

    return {"error": "File not found"}

 

다운로드는 현재 선택한 파일을 다운 받고, 

업로드시에는 아래처럼 파일을 선택하여 업로드!

 

 

 

 

- 파일삭제

@router.post("/dir/Del") # 삭제 버튼 만들것
async def Del_file(file_path: str):

    Del_command = [
        'rm',
        '-r',
        file_path
    ]
    
    subprocess.check_output(Del_command, text=True)

 

 

 

- 디렉토리 생성

@router.post("/dir/mkdir")# 디렉토리 생성
async def mkdir(directory_path : str):

    if not os.path.exists(directory_path):
        os.mkdir(directory_path)
        print(f"디렉토리 '{directory_path}' 생성됨")
    else:
        print(f"디렉토리 '{directory_path}' 이미 존재함")

 

 

-  압축해제

Zipfile 라이브러리를 사용하여 실행하는데...

** 문제 ** 리눅스환경에서 압축한 파일과 윈도우 환경에서 압축한 파일의 encoding이 같지 않아... 사용할때 유의!

# 압축
@router.post("/dir/zip")
async def zip(directory_path : str, sort: str):

    if sort=='0': # 파일
        file_name = os.path.basename(directory_path)
        zip_file_path = os.path.join(os.path.dirname(directory_path), f"{os.path.splitext(file_name)[0]}.zip")

        # 압축 실행
        with zipfile.ZipFile(zip_file_path, 'w') as zipf:
            arcname = os.path.basename(directory_path)
            zipf.write(directory_path, arcname)

    else: # 폴더 압축
        dir_name = os.path.basename(directory_path)
        parent_dir = os.path.dirname(directory_path)

        # 압축 파일 경로
        zip_file_path = os.path.join(parent_dir, dir_name + ".zip")

        with zipfile.ZipFile(zip_file_path, 'w') as zipf:
            for root, _, files in os.walk(directory_path):
                for file in files:
                    zipf.write(os.path.join(root, file), os.path.relpath(os.path.join(root, file), os.path.join(directory_path, '..')))



@router.post("/dir/Unzip")# 압축해제
async def Unzip(file_path : str):

    extract_to_directory = os.path.dirname(file_path)
    
    with zipfile.ZipFile(file_path, 'r') as zip_ref:

        for file_info in zip_ref.infolist():
 

            zip_ref.extract(file_info, extract_to_directory)
            new_file_path = os.path.join(extract_to_directory, os.path.basename(file_path).split('.')[0])

 

 

 

- 데이터 수 측정

일단은 jpg데이터의 수만 카운트 해주기로 하였습니다.

 

@router.post("/dir/Count")  ## 데이터 수 
async def count_jpg_files(directory : str): ## 이미지 count
    directory="./static"+directory

    all_files = os.listdir(directory)

    jpg_count = 0
    directory_count = 0

    for root, dirs, files in os.walk(directory):
        jpg_count += len([file for file in files if file.lower().endswith(".jpg")])
        directory_count += len(dirs)


    return JSONResponse(content={"jpg_count": jpg_count})

 

 

 

- 이미지 미리보기

따로 python 함수를 생성하지 않고, 클릭시 바로 사진을 변경하는 코드로 구성!

PreviewButton.addEventListener('click', async () => { // 미리보기 실행
            
            const Dir=document.getElementById('last_action_link');
            const savepath = Dir.value;


            const alldir = savepath.split('/');
            const fileName = alldir.pop(); // 이름
            const sort=fileName.split('.').reverse()[0];

            console.log(sort)


            if(image_format_List.includes(sort)){
                Pre_popup.style.display='block';

                imageElement.src = './static'+savepath;

                document.querySelector(".background").className = "background show";
            }
            else{
                
                console.log("사진만");
            }
        });

 

 

- 영상 변환 및 샘플 출력

 

이 부분은 ffmpeg 라이브러리를 사용하여 구성해 보았습니다. 

기본적으로 영상만 가능 하게 구성하였고

 

아래처럼 영상코덱, 포맷, 크기, 비트레이트, 채도 명도 명암을 조절 할 수 있습니다. 

 

샘플 버튼을 이용해서 현재 영상에 대한 변환 데이터를 바로 받아 볼 수 있고, 변환 버튼을 눌러 

영상이름 + 현재 시간 으로 새로운 영상이 만들어 지게 됩니다. 

 

 

함수는 아래처럼 구성해 주었고, 각각의 콤보박스에서 값을 불러와 ffmpeg 커맨드로 전달해 주었습니다. 

@router.post("/dir/Sample") # 샘플 보내기
async def ffmpeg_sample(request: Request):
    data = await request.json()
    file_path = data.get('file_path')
    saturation = float(data.get('saturation', 1.0))  
    brightness = float(data.get('brightness', 0.0))  
    contrast = float(data.get('contrast', 1.0))
    #checkVideo=int(data.get('check',0))


    savepath = './static' + file_path


    # 삭제
    file_path='./static/sample.jpg'
    
    try:
        # 파일이 존재하는지 확인
        if os.path.exists(file_path):
            # 파일 삭제
            os.remove(file_path)
            print(f"File '{file_path}' deleted successfully.")
        else:
            print(f"File '{file_path}' not found.")
    except Exception as e:
        print(f"An error occurred while deleting the file: {str(e)}")


    ffmpeg_command = [  # 비디오에서 1프레임 샘플 전송
        'ffmpeg',
        '-i', savepath,
        '-vf', f'eq=saturation={1+saturation/100}:brightness={brightness/100}:contrast={1+contrast/100}',
        '-frames:v', '1',
        './static/sample.jpg'
    ]

    print("savepath : " +  savepath)
    subprocess.check_output(ffmpeg_command, text=True)


    
@router.post("/dir/Conver") # 파일 변환
async def ffmpeg_Conversion(request: Request):
    data = await request.json()

    file_path = data.get('file_path') #변형할 파일
    saturation = float(data.get('saturation', 1.0))  
    brightness = float(data.get('brightness', 0.0))  
    contrast = float(data.get('contrast', 1.0))
    size = data.get('size','3840x2160')
    bitlayer = data.get('bitlayer',8000)
    codec = data.get('codec','libx264')
    formatstr=data.get('formatstr','MP4')


    print("--------------------------------------------------------")
    print(formatstr)


    savepath = './static' + file_path

    directory = os.path.dirname(savepath)
    last_directory = os.path.basename(savepath)
    
    last_directory=str(last_directory).split('.')[0]
    print("last_directory : ---------------------" + last_directory)

    # 날짜와 시간을 형식에 맞게 포맷팅
    current_datetime = datetime.datetime.now()
    formatted_datetime = current_datetime.strftime('%Y-%m-%d-%H-%M-%S-%f')


    ffmpeg_command = [
        'ffmpeg',
        '-i', savepath, ## 파일 위치
        '-vf', f'eq=saturation={1+saturation/100}:brightness={brightness/100}:contrast={1+contrast/100}',
        '-s', size,
        '-c:v', codec,
        '-preset', 'medium',
        '-b:v', bitlayer,
        directory+'/'+last_directory+"_"+formatted_datetime+"."+formatstr
    ]

    subprocess.check_output(ffmpeg_command, text=True)

 

 

주요기능들은 여기까지!

 

실행 및 더더 자세한 내용이 필요하다면!

아래 깃허브에서 다운 받아 실행 해 주세요!

 

GitHub - jhyun-lee/FastApi-DirectoryGui

Contribute to jhyun-lee/FastApi-DirectoryGui development by creating an account on GitHub.

github.com

 

 

끝!

 

 

 

 

틀린 점이 있다면 댓 달아주세요!

 

 

댓글