Update app.py
Browse files
app.py
CHANGED
@@ -35,6 +35,8 @@ from transformers import pipeline
|
|
35 |
from transformers import AutoModelForImageSegmentation
|
36 |
from torchvision import transforms
|
37 |
from moviepy import VideoFileClip, vfx, concatenate_videoclips, ImageSequenceClip, concatenate_audioclips, AudioFileClip, CompositeAudioClip
|
|
|
|
|
38 |
import time
|
39 |
from concurrent.futures import ThreadPoolExecutor
|
40 |
|
@@ -628,12 +630,46 @@ def merge_videos_with_audio(video_files, audio_file, audio_volume, output_fps):
|
|
628 |
|
629 |
status = f"{len(video_paths)}๊ฐ์ ๋น๋์ค ๋ก๋ ์ค..."
|
630 |
|
631 |
-
# ๋น๋์ค ํด๋ฆฝ ๋ก๋
|
632 |
video_clips = []
|
|
|
|
|
|
|
633 |
for i, video_path in enumerate(video_paths):
|
634 |
status = f"๋น๋์ค {i+1}/{len(video_paths)} ๋ก๋ ์ค: {os.path.basename(video_path)}"
|
635 |
clip = VideoFileClip(video_path)
|
636 |
video_clips.append(clip)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
637 |
|
638 |
# ์ฒซ ๋ฒ์งธ ๋น๋์ค์ FPS๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ฌ์ฉ
|
639 |
if output_fps == 0:
|
@@ -642,22 +678,31 @@ def merge_videos_with_audio(video_files, audio_file, audio_volume, output_fps):
|
|
642 |
status = "๋น๋์ค ๋ณํฉ ์ค..."
|
643 |
|
644 |
# ๋น๋์ค ๋ณํฉ
|
645 |
-
final_video = concatenate_videoclips(
|
646 |
|
647 |
# ์ค๋์ค ์ฒ๋ฆฌ
|
648 |
if audio_file:
|
649 |
status = "์ค๋์ค ์ฒ๋ฆฌ ์ค..."
|
650 |
|
651 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
652 |
# ์ค๋์ค ๋ก๋
|
653 |
-
if
|
654 |
# ๋น๋์ค ํ์ผ์์ ์ค๋์ค ์ถ์ถ
|
655 |
-
temp_video = VideoFileClip(
|
656 |
audio_clip = temp_video.audio
|
657 |
temp_video.close()
|
658 |
else:
|
659 |
# ์ค๋์ค ํ์ผ ์ง์ ๋ก๋
|
660 |
-
audio_clip = AudioFileClip(
|
661 |
|
662 |
if audio_clip is None:
|
663 |
raise ValueError("์ค๋์ค๋ฅผ ๋ก๋ํ ์ ์์ต๋๋ค.")
|
@@ -680,38 +725,59 @@ def merge_videos_with_audio(video_files, audio_file, audio_volume, output_fps):
|
|
680 |
looped_audio = concatenate_audioclips(audio_clips_list)
|
681 |
audio_clip = looped_audio.subclip(0, video_duration)
|
682 |
|
683 |
-
# ๊ธฐ์กด
|
684 |
-
|
685 |
-
|
686 |
-
|
687 |
-
|
688 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
689 |
|
690 |
except Exception as e:
|
691 |
-
logging.
|
692 |
# ์ค๋์ค ์ฒ๋ฆฌ ์คํจํด๋ ๋น๋์ค๋ ๊ณ์ ์ฒ๋ฆฌ
|
|
|
693 |
|
694 |
status = "๋น๋์ค ์ ์ฅ ์ค..."
|
695 |
|
696 |
# ์์ ํ์ผ๋ก ์ ์ฅ
|
697 |
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_file:
|
698 |
temp_filepath = temp_file.name
|
|
|
|
|
699 |
final_video.write_videofile(
|
700 |
temp_filepath,
|
701 |
fps=output_fps,
|
702 |
codec="libx264",
|
703 |
-
audio_codec="aac"
|
|
|
|
|
|
|
704 |
)
|
705 |
|
706 |
# ๋ฆฌ์์ค ์ ๋ฆฌ
|
707 |
for clip in video_clips:
|
708 |
clip.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
709 |
final_video.close()
|
710 |
|
711 |
-
return temp_filepath, f"โ
์ฑ๊ณต์ ์ผ๋ก {len(video_paths)}๊ฐ์ ๋น๋์ค๋ฅผ ๋ณํฉํ์ต๋๋ค!"
|
712 |
|
713 |
except Exception as e:
|
714 |
logging.error(f"Video merge error: {str(e)}")
|
|
|
|
|
715 |
return None, f"โ ์ค๋ฅ ๋ฐ์: {str(e)}"
|
716 |
|
717 |
# CSS
|
@@ -955,9 +1021,10 @@ with demo:
|
|
955 |
|
956 |
with gr.Group(elem_classes="panel-box"):
|
957 |
gr.Markdown("### ๐ต ์ค๋์ค ์ค์ (์ ํ)")
|
|
|
958 |
|
959 |
audio_file = gr.Audio(
|
960 |
-
label="์ค๋์ค ํ์ผ",
|
961 |
type="filepath",
|
962 |
sources=["upload"]
|
963 |
)
|
@@ -971,6 +1038,12 @@ with demo:
|
|
971 |
info="100% = ์๋ณธ ๋ณผ๋ฅจ"
|
972 |
)
|
973 |
|
|
|
|
|
|
|
|
|
|
|
|
|
974 |
with gr.Group(elem_classes="panel-box"):
|
975 |
gr.Markdown("### โ๏ธ ํธ์ง ์ค์ ")
|
976 |
|
@@ -982,6 +1055,13 @@ with demo:
|
|
982 |
label="์ถ๋ ฅ FPS (0 = ์ฒซ ๋ฒ์งธ ๋น๋์ค์ FPS ์ฌ์ฉ)"
|
983 |
)
|
984 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
985 |
merge_videos_btn = gr.Button("๐ฌ ๋น๋์ค ๋ณํฉ", variant="primary", elem_id="merge-btn")
|
986 |
|
987 |
# ์ถ๋ ฅ ์ปฌ๋ผ
|
@@ -999,7 +1079,14 @@ with demo:
|
|
999 |
3. (์ ํ) ์ค๋์ค ํ์ผ์ ์ถ๊ฐํ๊ณ ๋ณผ๋ฅจ์ ์กฐ์ ํ์ธ์
|
1000 |
4. '๋น๋์ค ๋ณํฉ' ๋ฒํผ์ ํด๋ฆญํ์ธ์
|
1001 |
|
1002 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1003 |
""")
|
1004 |
|
1005 |
# ๋ค์ฏ ๋ฒ์งธ ํญ: ๋น๋์ค ๋ฐฐ๊ฒฝ์ ๊ฑฐ/ํฉ์ฑ
|
|
|
35 |
from transformers import AutoModelForImageSegmentation
|
36 |
from torchvision import transforms
|
37 |
from moviepy import VideoFileClip, vfx, concatenate_videoclips, ImageSequenceClip, concatenate_audioclips, AudioFileClip, CompositeAudioClip
|
38 |
+
from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip
|
39 |
+
from moviepy.video.VideoClip import ColorClip
|
40 |
import time
|
41 |
from concurrent.futures import ThreadPoolExecutor
|
42 |
|
|
|
630 |
|
631 |
status = f"{len(video_paths)}๊ฐ์ ๋น๋์ค ๋ก๋ ์ค..."
|
632 |
|
633 |
+
# ๋น๋์ค ํด๋ฆฝ ๋ก๋ ๋ฐ ํฌ๊ธฐ ํ์ธ
|
634 |
video_clips = []
|
635 |
+
max_width = 0
|
636 |
+
max_height = 0
|
637 |
+
|
638 |
for i, video_path in enumerate(video_paths):
|
639 |
status = f"๋น๋์ค {i+1}/{len(video_paths)} ๋ก๋ ์ค: {os.path.basename(video_path)}"
|
640 |
clip = VideoFileClip(video_path)
|
641 |
video_clips.append(clip)
|
642 |
+
|
643 |
+
# ์ต๋ ํฌ๊ธฐ ์ถ์
|
644 |
+
if clip.w > max_width:
|
645 |
+
max_width = clip.w
|
646 |
+
if clip.h > max_height:
|
647 |
+
max_height = clip.h
|
648 |
+
|
649 |
+
# ์์ ๋ณ์ ์ ๋ฆฌ๋ฅผ ์ํ ๋ฐฐ๊ฒฝ ํด๋ฆฝ ์ ์ฅ
|
650 |
+
background_clips = []
|
651 |
+
|
652 |
+
# ๋ชจ๋ ๋น๋์ค๋ฅผ ์ต๋ ํฌ๊ธฐ๋ก ์กฐ์ (์๋ณธ ํ์ง ์ ์ง)
|
653 |
+
resized_clips = []
|
654 |
+
for clip in video_clips:
|
655 |
+
if clip.w != max_width or clip.h != max_height:
|
656 |
+
# ํจ๋ฉ ์ถ๊ฐ ๋ฐฉ์ (์๋ณธ ํ์ง ์ ์ง)
|
657 |
+
# ๊ฒ์์ ๋ฐฐ๊ฒฝ ์์ฑ
|
658 |
+
background = ColorClip(size=(max_width, max_height), color=(0,0,0), duration=clip.duration)
|
659 |
+
background_clips.append(background)
|
660 |
+
|
661 |
+
# ํด๋ฆฝ์ ์ค์์ ๋ฐฐ์น
|
662 |
+
x_center = (max_width - clip.w) // 2
|
663 |
+
y_center = (max_height - clip.h) // 2
|
664 |
+
|
665 |
+
resized_clip = CompositeVideoClip([
|
666 |
+
background,
|
667 |
+
clip.set_position((x_center, y_center))
|
668 |
+
])
|
669 |
+
|
670 |
+
resized_clips.append(resized_clip)
|
671 |
+
else:
|
672 |
+
resized_clips.append(clip)
|
673 |
|
674 |
# ์ฒซ ๋ฒ์งธ ๋น๋์ค์ FPS๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ฌ์ฉ
|
675 |
if output_fps == 0:
|
|
|
678 |
status = "๋น๋์ค ๋ณํฉ ์ค..."
|
679 |
|
680 |
# ๋น๋์ค ๋ณํฉ
|
681 |
+
final_video = concatenate_videoclips(resized_clips, method="compose")
|
682 |
|
683 |
# ์ค๋์ค ์ฒ๋ฆฌ
|
684 |
if audio_file:
|
685 |
status = "์ค๋์ค ์ฒ๋ฆฌ ์ค..."
|
686 |
|
687 |
try:
|
688 |
+
# ์ค๋์ค ํ์ผ ๊ฒฝ๋ก ํ์ธ
|
689 |
+
if isinstance(audio_file, str):
|
690 |
+
audio_path = audio_file
|
691 |
+
else:
|
692 |
+
# gr.Audio์์ ๋ฐํ๋ ํํ์ธ ๊ฒฝ์ฐ
|
693 |
+
audio_path = audio_file
|
694 |
+
|
695 |
+
logging.info(f"Processing audio from: {audio_path}")
|
696 |
+
|
697 |
# ์ค๋์ค ๋ก๋
|
698 |
+
if audio_path.endswith(('.mp4', '.avi', '.mov', '.mkv')):
|
699 |
# ๋น๋์ค ํ์ผ์์ ์ค๋์ค ์ถ์ถ
|
700 |
+
temp_video = VideoFileClip(audio_path)
|
701 |
audio_clip = temp_video.audio
|
702 |
temp_video.close()
|
703 |
else:
|
704 |
# ์ค๋์ค ํ์ผ ์ง์ ๋ก๋
|
705 |
+
audio_clip = AudioFileClip(audio_path)
|
706 |
|
707 |
if audio_clip is None:
|
708 |
raise ValueError("์ค๋์ค๋ฅผ ๋ก๋ํ ์ ์์ต๋๋ค.")
|
|
|
725 |
looped_audio = concatenate_audioclips(audio_clips_list)
|
726 |
audio_clip = looped_audio.subclip(0, video_duration)
|
727 |
|
728 |
+
# ๊ธฐ์กด ์ค๋์ค ์ ๊ฑฐํ๊ณ ์ ์ค๋์ค๋ก ๊ต์ฒด
|
729 |
+
# (๊ธฐ์กด ์ค๋์ค์ ํฉ์ฑํ๋ ค๋ฉด ์๋ ์ฃผ์ ํด์ )
|
730 |
+
final_video = final_video.set_audio(audio_clip)
|
731 |
+
|
732 |
+
# ๊ธฐ์กด ์ค๋์ค์ ์ ์ค๋์ค ํฉ์ฑ์ ์ํ๋ ๊ฒฝ์ฐ:
|
733 |
+
# if final_video.audio:
|
734 |
+
# final_audio = CompositeAudioClip([final_video.audio, audio_clip])
|
735 |
+
# final_video = final_video.set_audio(final_audio)
|
736 |
+
# else:
|
737 |
+
# final_video = final_video.set_audio(audio_clip)
|
738 |
+
|
739 |
+
logging.info("Audio successfully added to video")
|
740 |
|
741 |
except Exception as e:
|
742 |
+
logging.error(f"์ค๋์ค ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}")
|
743 |
# ์ค๋์ค ์ฒ๋ฆฌ ์คํจํด๋ ๋น๋์ค๋ ๊ณ์ ์ฒ๋ฆฌ
|
744 |
+
status = f"์ค๋์ค ์ฒ๋ฆฌ ์คํจ: {str(e)}, ๋น๋์ค๋ง ๋ณํฉํฉ๋๋ค."
|
745 |
|
746 |
status = "๋น๋์ค ์ ์ฅ ์ค..."
|
747 |
|
748 |
# ์์ ํ์ผ๋ก ์ ์ฅ
|
749 |
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_file:
|
750 |
temp_filepath = temp_file.name
|
751 |
+
|
752 |
+
# ์ฝ๋ฑ ์ค์ - ์๋ณธ ํ์ง ์ ์ง
|
753 |
final_video.write_videofile(
|
754 |
temp_filepath,
|
755 |
fps=output_fps,
|
756 |
codec="libx264",
|
757 |
+
audio_codec="aac",
|
758 |
+
preset="medium", # ํ์ง ์ค์
|
759 |
+
bitrate="5000k", # ๋นํธ๋ ์ดํธ ์ค์ ์ผ๋ก ํ์ง ์ ์ง
|
760 |
+
audio_bitrate="192k"
|
761 |
)
|
762 |
|
763 |
# ๋ฆฌ์์ค ์ ๋ฆฌ
|
764 |
for clip in video_clips:
|
765 |
clip.close()
|
766 |
+
for clip in background_clips:
|
767 |
+
clip.close()
|
768 |
+
for i, clip in enumerate(resized_clips):
|
769 |
+
if clip != video_clips[i] if i < len(video_clips) else True: # ๋ฆฌ์ฌ์ด์ฆ๋ ๊ฒฝ์ฐ๋ง
|
770 |
+
clip.close()
|
771 |
+
if audio_file and 'audio_clip' in locals():
|
772 |
+
audio_clip.close()
|
773 |
final_video.close()
|
774 |
|
775 |
+
return temp_filepath, f"โ
์ฑ๊ณต์ ์ผ๋ก {len(video_paths)}๊ฐ์ ๋น๋์ค๋ฅผ ๋ณํฉํ์ต๋๋ค! (ํฌ๊ธฐ: {max_width}x{max_height})"
|
776 |
|
777 |
except Exception as e:
|
778 |
logging.error(f"Video merge error: {str(e)}")
|
779 |
+
import traceback
|
780 |
+
traceback.print_exc()
|
781 |
return None, f"โ ์ค๋ฅ ๋ฐ์: {str(e)}"
|
782 |
|
783 |
# CSS
|
|
|
1021 |
|
1022 |
with gr.Group(elem_classes="panel-box"):
|
1023 |
gr.Markdown("### ๐ต ์ค๋์ค ์ค์ (์ ํ)")
|
1024 |
+
gr.Markdown("**์ฃผ์**: ์
๋ก๋ํ ์ค๋์ค๊ฐ ๋น๋์ค์ ๊ธฐ์กด ์ค๋์ค๋ฅผ ์์ ํ ๋์ฒดํฉ๋๋ค.")
|
1025 |
|
1026 |
audio_file = gr.Audio(
|
1027 |
+
label="์ค๋์ค ํ์ผ (MP3, WAV, M4A ๋ฑ)",
|
1028 |
type="filepath",
|
1029 |
sources=["upload"]
|
1030 |
)
|
|
|
1038 |
info="100% = ์๋ณธ ๋ณผ๋ฅจ"
|
1039 |
)
|
1040 |
|
1041 |
+
gr.Markdown("""
|
1042 |
+
**์ค๋์ค ์ต์
**:
|
1043 |
+
- ์ค๋์ค๊ฐ ๋น๋์ค๋ณด๋ค ์งง์ผ๋ฉด ์๋์ผ๋ก ๋ฐ๋ณต๋ฉ๋๋ค
|
1044 |
+
- ์ค๋์ค๊ฐ ๋น๋์ค๋ณด๋ค ๊ธธ๋ฉด ๋น๋์ค ๊ธธ์ด์ ๋ง์ถฐ ์๋ฆฝ๋๋ค
|
1045 |
+
""")
|
1046 |
+
|
1047 |
with gr.Group(elem_classes="panel-box"):
|
1048 |
gr.Markdown("### โ๏ธ ํธ์ง ์ค์ ")
|
1049 |
|
|
|
1055 |
label="์ถ๋ ฅ FPS (0 = ์ฒซ ๋ฒ์งธ ๋น๋์ค์ FPS ์ฌ์ฉ)"
|
1056 |
)
|
1057 |
|
1058 |
+
gr.Markdown("""
|
1059 |
+
**ํฌ๊ธฐ ์ฒ๋ฆฌ**:
|
1060 |
+
- ๋ชจ๋ ๋น๋์ค๋ ๊ฐ์ฅ ํฐ ๋น๋์ค์ ํฌ๊ธฐ์ ๋ง์ถฐ์ง๋๋ค
|
1061 |
+
- ์์ ๋น๋์ค๋ ๊ฒ์์ ํจ๋ฉ์ด ์ถ๊ฐ๋์ด ์ค์์ ๋ฐฐ์น๋ฉ๋๋ค
|
1062 |
+
- ์๋ณธ ํ์ง์ด ๊ทธ๋๋ก ์ ์ง๋ฉ๋๋ค
|
1063 |
+
""")
|
1064 |
+
|
1065 |
merge_videos_btn = gr.Button("๐ฌ ๋น๋์ค ๋ณํฉ", variant="primary", elem_id="merge-btn")
|
1066 |
|
1067 |
# ์ถ๋ ฅ ์ปฌ๋ผ
|
|
|
1079 |
3. (์ ํ) ์ค๋์ค ํ์ผ์ ์ถ๊ฐํ๊ณ ๋ณผ๋ฅจ์ ์กฐ์ ํ์ธ์
|
1080 |
4. '๋น๋์ค ๋ณํฉ' ๋ฒํผ์ ํด๋ฆญํ์ธ์
|
1081 |
|
1082 |
+
**ํน์ง**:
|
1083 |
+
- โ
์์ ๋น๋์ค๋ ํจ๋ฉ ์ถ๊ฐ๋ก ์๋ณธ ํ์ง ์ ์ง
|
1084 |
+
- โ
์
๋ก๋ํ ์ค๋์ค๊ฐ ์ ์ฒด ๋น๋์ค์ ์ ์ฉ๋ฉ๋๋ค
|
1085 |
+
- โ
์๋ณธ ํ์ง์ด ์ต๋ํ ์ ์ง๋ฉ๋๋ค
|
1086 |
+
|
1087 |
+
**ํ**:
|
1088 |
+
- ํ์ผ๋ช
์ 01.mp4, 02.mp4, 03.mp4 ํ์์ผ๋ก ์ง์ ํ๋ฉด ์์ ๊ด๋ฆฌ๊ฐ ์ฝ์ต๋๋ค
|
1089 |
+
- ์ค๋์ค๋ฅผ ์ถ๊ฐํ๋ฉด ๊ธฐ์กด ๋น๋์ค์ ์ค๋์ค๋ ๋์ฒด๋ฉ๋๋ค
|
1090 |
""")
|
1091 |
|
1092 |
# ๋ค์ฏ ๋ฒ์งธ ํญ: ๋น๋์ค ๋ฐฐ๊ฒฝ์ ๊ฑฐ/ํฉ์ฑ
|