음원 및 영상 파일 업로드
- 현재 프로젝트에서 음원 및 영상 파일 s3 업로드 기능 구현을 하게 되었다.
- 첫 번째 방법은
로컬에서만 성공
하였다. 해당 방법으로 s3에 파일을 압축해 올리고 싶었으나 경로를 찾지 못하는 src/tmp/초특가 야놀자 15s.wav: No such file or directory
에러를 얻게 되었다.
- 두 번째 방법은
로컬과 개발 서버 모두 성공
하였다. 하지만 해당 방법은 파일을 압축하지 않고 곧바로 s3에 업로드하는 방식이다.
- 서버에서 받는 파일의 크기를 애초에 제한한다면 나쁘지 않은 방법이라 생각한다.
- 큰 사이즈의 파일에 대한 테스트가 필요하다.
- 두 방법의 공통점은
파일 경로
를 활용한다는 점이다. 파일을 우선 로컬에 저장하고, 해당 경로를 전달해 그곳에 있는 파일을 가져다 압축 후 업로드 혹은 바로 업로드하는 방식이다.
s3 함수
- 두 가지 방법 모두에서 공통적으로 쓰였으며, s3에 파일을 업로드해준다.
def upload_to_s3(local_file_path, s3_bucket, s3_key):
try:
s3 = boto3.client("s3")
s3.upload_file(local_file_path, s3_bucket, s3_key)
return True
except NoCredentialsError:
print("Credentials not available")
return False
ffmpeg subprocess 를 사용한 압축
celery 함수
- ffmpeg를 사용해 파일을 압축하는 방식을 채택하였다. 음원과 영상 모두 이름만 다르고 기능은 똑같다.
- 해당 방법은 ffmpeg 의 subprocess 를 사용해 음원 및 영상 파일을 압축한다.
- 해당 방법이 개발 서버에서 실패했던 이유는
경로를 찾지 못해서
이다.
- 이를 해결하기 위해 Dockerfile에
tmp/ 경로를 만들어봤으나
여전히 경로를 찾지 못하여 사용하지 못하게 되었다. (서버는 ec2)
- 음원의 경우 테스트한 파일들의 크기가 크지 않아 눈에 띄게 압축되지는 않았지만, 영상의 경우 크기가 확연히 줄어든 것을 확인할 수 있었다.
import logging
import os
import subprocess
from celery import shared_task
from django.conf import settings
from django.core.files.temp import NamedTemporaryFile
from app.contact.models import ContactAudio
from app.filmography.models import FilmographyAudio, FilmographyVideo, TaskPoller
from app.filmography.v1.utils import upload_to_s3
from app.message.models import Message
logger = logging.getLogger(__name__)
@shared_task
def audio(
task_poller_id=None,
filmography_audio_id=None,
message_id=None,
contact_audio_id=None,
files=None,
s3_object_key=None,
):
"""
files = request.FILES["audio_file"]
- "-y" -> 파일 덮어쓰기 설정 (해당 설정이 없을 때 같은 음원이 여러번 들어오면 덮어쓸 것인지 물어보며 대답을 해야 압축이 진행됨)
- "-i" -> 입력파일 지정 (해당 플래그 뒤에 입력 파일의 경로가 와야 함)
- "-b:a", "64k" -> 오디오 비트레이트 설정
- "-ar", "22050" -> 오디오 샘플레이트 설정
"""
task_poller = TaskPoller.objects.get(id=task_poller_id)
try:
logger.info("오디오 압축 시작")
logger.info(
f"filmography_audio_id : {filmography_audio_id}, message_id : {message_id}, contact_audio_id: {contact_audio_id}"
)
with NamedTemporaryFile(suffix=".wav", delete=True) as temp_file:
output_file = temp_file.name
command = [
"ffmpeg",
"-y",
"-i",
files,
"-b:a",
"64k",
"-ar",
"22050",
output_file,
]
subprocess.run(command, check=True)
if upload_to_s3(output_file, settings.AWS_STORAGE_BUCKET_NAME, f"_media/{s3_object_key}"):
task_poller.is_completed = True
if filmography_audio_id:
task_poller.audio_id = filmography_audio_id
elif message_id:
task_poller.message_id = message_id
elif contact_audio_id:
task_poller.contact_audio_id = contact_audio_id
task_poller.save()
logger.info(f"task_poller 완료 여부 : {task_poller.is_completed}")
os.remove(files)
return s3_object_key
else:
logger.info("File upload to S3 failed")
except subprocess.CalledProcessError as e:
logger.info(f"오디오 압축 실패 <- {e}")
task_poller.fail_reason = e
task_poller.save()
FilmographyAudio.objects.filter(id=filmography_audio_id).delete()
Message.objects.filter(id=message_id).delete()
ContactAudio.objects.filter(id=contact_audio_id).delete()
raise Exception(e)
finally:
temp_file.close()
serializer
delay()
를 사용해 celery에게 음원 및 영상 파일 압축을 맡겼다.
- 사용자에게 음원 또는 영상 파일이 올라가고 있다는 것을 알려주기 위해 일단 task_poller_id를 넘겼고, 해당 task_poller_id를 통해 task_poller를 retrieve 하도록 하였다.
- is_completed=True 인 경우, 파일 업로드가 완료된 상태이다.
FileSystemStorage()
를 활용해 파일을 로컬에 저장한다. 이곳에 저장된 파일을 celery에서 끌어다 압축을 하게 된다.
def create(self, validated_data):
with transaction.atomic():
instrument_set = validated_data.pop("instrument_id_list", None)
task_poller = TaskPoller.objects.create(type=TaskPollerChoices.AUDIO)
audio_file = validated_data.pop("audio")
uploaded_file, exp = audio_file._name.split(".")
s3_object_key = f"filmography/audio/{uploaded_file}.flac"
storage = FileSystemStorage()
storage.save(uploaded_file, File(audio_file))
validated_data["audio"] = s3_object_key
instance = FilmographyAudio.objects.create(**validated_data)
audio.delay(
task_poller_id=task_poller.id,
filmography_audio_id=instance.id,
message_id=None,
files=storage.path(uploaded_file),
s3_object_key=s3_object_key,
)
if instrument_set:
instance.tag_set.set(instrument_set)
return task_poller
s3에 바로 업로드
def update(self, instance, validated_data):
with transaction.atomic():
audio_file = validated_data.pop("audio", None)
if audio_file:
s3_object_key = f"contact/audio/{audio_file._name}"
tmp_dir = f"/tmp/{audio_file._name}"
with open(tmp_dir, "wb") as tmp_audio_file:
tmp_audio_file.write(audio_file.read())
if upload_to_s3(tmp_dir, settings.AWS_STORAGE_BUCKET_NAME, f"_media/{s3_object_key}"):
contact_audio = ContactAudio.objects.create(contact=self.instance, audio=s3_object_key)
os.remove(tmp_dir)
else:
raise ValidationError("File upload to S3 failed")
return contact_audio