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 |
# ๋ค์ฏ ๋ฒ์งธ ํญ: ๋น๋์ค ๋ฐฐ๊ฒฝ์ ๊ฑฐ/ํฉ์ฑ
|