Spaces:
Runtime error
Runtime error
update files
Browse files- .gitattributes +36 -0
- .github/workflows/run.yaml +20 -0
- .gitignore +7 -0
- README.md +135 -0
- act.bat +1 -0
- app.py +39 -0
- main.py +60 -0
- modules/BlurredBackgroundEmbedder.py +42 -0
- modules/FrameVideoCreator.py +66 -0
- modules/Transition/SimpleVideoMerger.py +52 -0
- modules/Transition/VideoMergerWithSmoothTransition.py +64 -0
- modules/utils/image_frame_generator.py +49 -0
- modules/utils/v_image_blurred_utils.py +48 -0
- modules/video_merger.py +17 -0
- requirements.txt +3 -0
.gitattributes
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
17 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
18 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
19 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
20 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
21 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
22 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
23 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
24 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
25 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
26 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
27 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
30 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
31 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
32 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
33 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
34 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
35 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
36 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/run.yaml
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Sync to Hugging Face hub
|
2 |
+
on:
|
3 |
+
push:
|
4 |
+
branches: [main]
|
5 |
+
|
6 |
+
# to run this workflow manually from the Actions tab
|
7 |
+
workflow_dispatch:
|
8 |
+
|
9 |
+
jobs:
|
10 |
+
sync-to-hub:
|
11 |
+
runs-on: ubuntu-latest
|
12 |
+
steps:
|
13 |
+
- uses: actions/checkout@v3
|
14 |
+
with:
|
15 |
+
fetch-depth: 0
|
16 |
+
lfs: true
|
17 |
+
- name: Push to hub
|
18 |
+
env:
|
19 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
20 |
+
run: git push --force https://MakiAi:$HF_TOKEN@huggingface.co/spaces/MakiAi/Image2VideoProcessingPipelin main
|
.gitignore
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
image/*
|
2 |
+
*.mp4
|
3 |
+
modules/__pycache__
|
4 |
+
modules/Transition/__pycache__/
|
5 |
+
modules/utils/__pycache__/
|
6 |
+
_tmp/
|
7 |
+
__pycache__/
|
README.md
ADDED
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: Image to Video Processing Pipelin
|
3 |
+
emoji: 🎥
|
4 |
+
colorFrom: green
|
5 |
+
colorTo: red
|
6 |
+
sdk: gradio
|
7 |
+
sdk_version: 3.41.2
|
8 |
+
app_file: app.py
|
9 |
+
pinned: false
|
10 |
+
license: apache-2.0
|
11 |
+
---
|
12 |
+
|
13 |
+
# VideoProcessingPipeline(SNS投稿動画生成ツール)
|
14 |
+
|
15 |
+
SNSの投稿は、画像よりも動画の方が注目を集めやすいことが多いです。しかし、動画を作成するのは手間がかかるもの。そこで、手持ちの画像から自動で魅力的な動画を生成する方法をご紹介します。
|
16 |
+
|
17 |
+

|
18 |
+
|
19 |
+
|
20 |
+
## 概要
|
21 |
+
|
22 |
+
### ステップ1: 必要なツールの準備
|
23 |
+
- Python環境
|
24 |
+
- OpenCV
|
25 |
+
- PIL (Pillow)
|
26 |
+
|
27 |
+
これらはpipを使って簡単にインストールできます。
|
28 |
+
|
29 |
+
```bash
|
30 |
+
|
31 |
+
pip install opencv-python pillow
|
32 |
+
```
|
33 |
+
|
34 |
+
|
35 |
+
### ステップ2: 画像の準備
|
36 |
+
|
37 |
+
まず、動画に変換したい画像を一つのフォルダにまとめます。このフォルダのパスを後で使用します。
|
38 |
+
### ステップ3: 画像のブラー処理
|
39 |
+
|
40 |
+
動画を作成する前に、画像にブラー効果を適用します。これにより、動画が洗練された雰囲気を持つようになります。
|
41 |
+
|
42 |
+
以下のクラス、`BlurredBackgroundEmbedder`を使用して、画像にブラー効果を適用します。
|
43 |
+
|
44 |
+
```python
|
45 |
+
|
46 |
+
processor = BlurredBackgroundEmbedder(r"path/to/your/image_folder")
|
47 |
+
processor.process_all_images()
|
48 |
+
```
|
49 |
+
|
50 |
+
|
51 |
+
### ステップ4: 動画の作成
|
52 |
+
|
53 |
+
ブラー処理された画像を使用して、動画を作成します。この動画は、画像が上から下にスクロールするアニメーションを持っています。
|
54 |
+
|
55 |
+
以下のクラス、`FrameVideoCreator`を使用して動画を作成します。
|
56 |
+
|
57 |
+
```python
|
58 |
+
|
59 |
+
video_creator = FrameVideoCreator()
|
60 |
+
video_creator.process_folder(r"path/to/your/image_folder_Blurred")
|
61 |
+
```
|
62 |
+
|
63 |
+
|
64 |
+
### ステップ5: 動画の連結
|
65 |
+
|
66 |
+
複数の動画を一つに連結します。また、動画間の遷移はスムーズなアルファブレンドが適用されます。
|
67 |
+
|
68 |
+
```python
|
69 |
+
|
70 |
+
merger = VideoMergerWithSmoothTransition()
|
71 |
+
input_folder_path = r"path/to/your/image_folder_Blurred_mov"
|
72 |
+
output_folder_path = f"{input_folder_path}_Final"
|
73 |
+
os.makedirs(output_folder_path, exist_ok=True)
|
74 |
+
output_video_path = os.path.join(output_folder_path, "concatenated_video.mp4")
|
75 |
+
merger.merge_videos(input_folder_path, output_video_path)
|
76 |
+
```
|
77 |
+
|
78 |
+
|
79 |
+
### ステップ6: 動画の確認
|
80 |
+
|
81 |
+
出力フォルダに保存された動画を確認します。この動画をSNSにアップロードすることで、魅力的な投稿を簡単に作成できます。
|
82 |
+
|
83 |
+
|
84 |
+
## 使用方法
|
85 |
+
|
86 |
+
`VideoProcessingPipeline`は、指定された画像フォルダ内の画像を処理し、動画を作成し、動画を連結する一連のタスクを実行するPythonクラスです。
|
87 |
+
|
88 |
+
### クイックスタート
|
89 |
+
|
90 |
+
1. まず、必要なモジュールをインポートします。
|
91 |
+
|
92 |
+
```python
|
93 |
+
|
94 |
+
from modules.utils import v_image_blurred_utils
|
95 |
+
from modules.BlurredBackgroundEmbedder import BlurredBackgroundEmbedder
|
96 |
+
from modules.FrameVideoCreator import FrameVideoCreator
|
97 |
+
from modules.Transition.VideoMergerWithSmoothTransition import VideoMergerWithSmoothTransition
|
98 |
+
```
|
99 |
+
|
100 |
+
|
101 |
+
1. `VideoProcessingPipeline`クラスを初期化します。このとき、画像が格納されているフォルダのパスを引数として渡します。
|
102 |
+
|
103 |
+
```python
|
104 |
+
|
105 |
+
pipeline = VideoProcessingPipeline(r"path/to/your/image_folder")
|
106 |
+
```
|
107 |
+
|
108 |
+
|
109 |
+
1. `execute_pipeline`メソッドを呼び出して、画像の処理、動画の作成、動画の連結を順番に実行します。
|
110 |
+
|
111 |
+
```python
|
112 |
+
|
113 |
+
pipeline.execute_pipeline()
|
114 |
+
```
|
115 |
+
|
116 |
+
|
117 |
+
### 内部の動作
|
118 |
+
- **画像のブラー処理** : 画像にブラー効果を適用し、新しいフォルダに保存します。
|
119 |
+
- **動画の作成** : ブラー処理された画像から、上から下に移動するアニメーション効果を持つ動画を作成します。
|
120 |
+
- **動画の連結** : すべての動画を一つの動画に連結します。動画間の遷移はスムーズなアルファブレンドが適用されます。
|
121 |
+
|
122 |
+
### 注意点
|
123 |
+
- 入力として渡される画像フォルダには、連結したい画像のみが含まれていることを確認してください。
|
124 |
+
- 生成される動画は、`image_folder_Blurred_mov_Final`という名前のフォルダに保存されます。
|
125 |
+
|
126 |
+
|
127 |
+
## 解説サイト
|
128 |
+
|
129 |
+
詳細な解説はこちら
|
130 |
+
|
131 |
+
https://hamaruki.com/automatically-generate-posted-videos-from-images/
|
132 |
+
|
133 |
+
## まとめ
|
134 |
+
|
135 |
+
Pythonを使用して、手持ちの画像からSNSに投稿するための動画を簡単に自動生成する方法を学びました。この方法を活用すれば、視覚的に魅力的なSNSの投稿を簡単に作成できます。初心者の方でも、ステップを追って実行すれば、手軽に動画を生成できるので、ぜひ試してみてください。
|
act.bat
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
conda activate vImage2MOV2
|
app.py
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import gradio as gr
|
3 |
+
from main import VideoProcessingPipeline
|
4 |
+
import shutil
|
5 |
+
|
6 |
+
def remove_temp_folder():
|
7 |
+
"""一時的なフォルダを削除する関数"""
|
8 |
+
temp_dir = "_tmp/temp_uploaded_images"
|
9 |
+
if os.path.exists(temp_dir):
|
10 |
+
shutil.rmtree(temp_dir)
|
11 |
+
|
12 |
+
def process_uploaded_images(files):
|
13 |
+
# _tmpフォルダ内に一時的なフォルダを作成して、アップロードされた画像を保存
|
14 |
+
temp_folder = "_tmp/temp_uploaded_images"
|
15 |
+
os.makedirs(temp_folder, exist_ok=True)
|
16 |
+
|
17 |
+
for file in files:
|
18 |
+
# 新しい保存先のパスを生成
|
19 |
+
new_path = os.path.join(temp_folder, os.path.basename(file.name))
|
20 |
+
# ファイルを新しい場所にコピー
|
21 |
+
shutil.copy(file.name, new_path)
|
22 |
+
|
23 |
+
# VideoProcessingPipelineを実行
|
24 |
+
pipeline = VideoProcessingPipeline(temp_folder)
|
25 |
+
pipeline.execute_pipeline()
|
26 |
+
|
27 |
+
# 最終的に生成された動画のパスを返す
|
28 |
+
return os.path.join(pipeline.final_folder, "concatenated_video.mp4")
|
29 |
+
|
30 |
+
demo = gr.Interface(
|
31 |
+
process_uploaded_images,
|
32 |
+
gr.File(file_count=5, file_types=[".jpg", ".jpeg", ".png"]),
|
33 |
+
gr.Video(),
|
34 |
+
title="Image to Video Processing"
|
35 |
+
)
|
36 |
+
|
37 |
+
if __name__ == "__main__":
|
38 |
+
remove_temp_folder() # 起動時に一時的なフォルダを削除
|
39 |
+
demo.launch()
|
main.py
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image, ImageFilter
|
2 |
+
import random
|
3 |
+
import os
|
4 |
+
from pathlib import Path
|
5 |
+
import cv2
|
6 |
+
import numpy as np
|
7 |
+
from modules.utils import v_image_blurred_utils
|
8 |
+
from modules.BlurredBackgroundEmbedder import BlurredBackgroundEmbedder
|
9 |
+
from modules.FrameVideoCreator import FrameVideoCreator
|
10 |
+
from modules.Transition.VideoMergerWithSmoothTransition import VideoMergerWithSmoothTransition
|
11 |
+
|
12 |
+
|
13 |
+
class VideoProcessingPipeline:
|
14 |
+
def __init__(self, image_folder: str):
|
15 |
+
"""
|
16 |
+
VideoProcessingPipeline クラスの初期化関数
|
17 |
+
|
18 |
+
引数:
|
19 |
+
- image_folder: 画像が格納されているフォルダのパス
|
20 |
+
"""
|
21 |
+
self.image_folder = image_folder
|
22 |
+
self.blurred_folder = f"{image_folder}_Blurred"
|
23 |
+
self.video_folder = f"{self.blurred_folder}_mov"
|
24 |
+
self.final_folder = f"{self.video_folder}_Final"
|
25 |
+
|
26 |
+
def process_images(self):
|
27 |
+
"""
|
28 |
+
画像のブラー処理を実行
|
29 |
+
"""
|
30 |
+
processor = BlurredBackgroundEmbedder(self.image_folder)
|
31 |
+
processor.process_all_images()
|
32 |
+
|
33 |
+
def create_videos_from_images(self):
|
34 |
+
"""
|
35 |
+
ブラー処理された画像から動画を作成
|
36 |
+
"""
|
37 |
+
video_creator = FrameVideoCreator()
|
38 |
+
video_creator.process_folder(self.blurred_folder)
|
39 |
+
|
40 |
+
def merge_videos(self):
|
41 |
+
"""
|
42 |
+
生成された動画を連結
|
43 |
+
"""
|
44 |
+
os.makedirs(self.final_folder, exist_ok=True)
|
45 |
+
output_video_path = os.path.join(self.final_folder, "concatenated_video.mp4")
|
46 |
+
merger = VideoMergerWithSmoothTransition()
|
47 |
+
merger.merge_videos(self.video_folder, output_video_path)
|
48 |
+
|
49 |
+
def execute_pipeline(self):
|
50 |
+
"""
|
51 |
+
画像の処理、動画の作成、動画の連結を順番に実行
|
52 |
+
"""
|
53 |
+
self.process_images()
|
54 |
+
self.create_videos_from_images()
|
55 |
+
self.merge_videos()
|
56 |
+
print("Video processing completed!")
|
57 |
+
|
58 |
+
if __name__ == '__main__':
|
59 |
+
pipeline = VideoProcessingPipeline(r"image\The-Multifaceted-Dawn")
|
60 |
+
pipeline.execute_pipeline()
|
modules/BlurredBackgroundEmbedder.py
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image, ImageFilter
|
2 |
+
import random
|
3 |
+
import os
|
4 |
+
from pathlib import Path
|
5 |
+
import cv2
|
6 |
+
import numpy as np
|
7 |
+
from modules.utils import v_image_blurred_utils
|
8 |
+
|
9 |
+
class BlurredBackgroundEmbedder:
|
10 |
+
def __init__(self, input_folder: str, height: int = 2000):
|
11 |
+
"""
|
12 |
+
ImageProcessor クラスの初期化関数
|
13 |
+
|
14 |
+
引数:
|
15 |
+
- input_folder: 画像が保存されているフォルダのパス
|
16 |
+
- height: 出力画像の希望の高さ(デフォルトは2000ピクセル)
|
17 |
+
"""
|
18 |
+
self.input_folder = input_folder
|
19 |
+
self.height = height
|
20 |
+
self.output_folder = input_folder + "_Blurred"
|
21 |
+
|
22 |
+
# 出力フォルダを作成
|
23 |
+
if not os.path.exists(self.output_folder):
|
24 |
+
os.makedirs(self.output_folder)
|
25 |
+
|
26 |
+
def process_all_images(self):
|
27 |
+
"""
|
28 |
+
指定されたフォルダ内のすべての画像を処理します。
|
29 |
+
"""
|
30 |
+
image_files = [f for f in os.listdir(self.input_folder) if os.path.isfile(os.path.join(self.input_folder, f))]
|
31 |
+
print("self.input_folder")
|
32 |
+
print(self.input_folder)
|
33 |
+
|
34 |
+
for image_file in image_files:
|
35 |
+
input_image_path = os.path.join(self.input_folder, image_file)
|
36 |
+
output_image_path = os.path.join(self.output_folder, image_file)
|
37 |
+
v_image_blurred_utils.embed_image_on_blurred_background(input_image_path, output_image_path)
|
38 |
+
|
39 |
+
if __name__ == '__main__':
|
40 |
+
# クラスの使用例
|
41 |
+
processor = BlurredBackgroundEmbedder(r"image\Echoes-of-Creation")
|
42 |
+
processor.process_all_images()
|
modules/FrameVideoCreator.py
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image, ImageFilter
|
2 |
+
import random
|
3 |
+
import os
|
4 |
+
from pathlib import Path
|
5 |
+
import cv2
|
6 |
+
import numpy as np
|
7 |
+
from modules.utils import image_frame_generator
|
8 |
+
|
9 |
+
class FrameVideoCreator:
|
10 |
+
def __init__(self, fps: int = 30):
|
11 |
+
"""
|
12 |
+
FrameVideoCreator クラスの初期化関数
|
13 |
+
|
14 |
+
引数:
|
15 |
+
- fps: 動画のフレームレート (デフォルトは30FPS)
|
16 |
+
"""
|
17 |
+
self.fps = fps
|
18 |
+
|
19 |
+
# 昇順(上から下)のエフェクトを初めに適用
|
20 |
+
self.ascending_order = True
|
21 |
+
|
22 |
+
def create_video_from_frames(self, frames: list, output_path: str):
|
23 |
+
"""
|
24 |
+
与えられた画像フレームのリストから動画を作成します。動画ごとに昇順と降順のエフェクトを交互に適用します。
|
25 |
+
|
26 |
+
引数:
|
27 |
+
- frames: 画像フレームのリスト
|
28 |
+
- output_path: 保存する動画のパス
|
29 |
+
"""
|
30 |
+
frames_bgr = [cv2.cvtColor(np.array(frame), cv2.COLOR_RGB2BGR) for frame in frames]
|
31 |
+
|
32 |
+
# 昇順か降順のエフェクトを適用するかに基づいて、フレームの順番を変更
|
33 |
+
if not self.ascending_order:
|
34 |
+
frames_bgr = list(reversed(frames_bgr))
|
35 |
+
|
36 |
+
# 次回の動画生成のためにエフェクトの順番を切り替え
|
37 |
+
self.ascending_order = not self.ascending_order
|
38 |
+
|
39 |
+
height, width, layers = frames_bgr[0].shape
|
40 |
+
size = (width, height)
|
41 |
+
out = cv2.VideoWriter(output_path, cv2.VideoWriter_fourcc(*'MP4V'), self.fps, size)
|
42 |
+
|
43 |
+
for frame in frames_bgr:
|
44 |
+
out.write(frame)
|
45 |
+
|
46 |
+
out.release()
|
47 |
+
|
48 |
+
def process_folder(self, input_folder: str):
|
49 |
+
"""
|
50 |
+
指定したフォルダ内の画像を処理し、動画を生成します。
|
51 |
+
|
52 |
+
引数:
|
53 |
+
- input_folder: 画像が保存されているフォルダのパス
|
54 |
+
"""
|
55 |
+
output_folder = f"{input_folder}_mov"
|
56 |
+
Path(output_folder).mkdir(parents=True, exist_ok=True)
|
57 |
+
|
58 |
+
for image_file in Path(input_folder).glob("*.png"):
|
59 |
+
frames = image_frame_generator.generate_centered_moving_frames(str(image_file))
|
60 |
+
output_video_path = Path(output_folder) / f"{image_file.stem}.mp4"
|
61 |
+
self.create_video_from_frames(frames, str(output_video_path))
|
62 |
+
|
63 |
+
if __name__ == '__main__':
|
64 |
+
# クラスの使用例
|
65 |
+
video_creator = FrameVideoCreator()
|
66 |
+
video_creator.process_folder(r"image/Echoes-of-Creation_Blurred")
|
modules/Transition/SimpleVideoMerger.py
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image, ImageFilter
|
2 |
+
import random
|
3 |
+
import os
|
4 |
+
from pathlib import Path
|
5 |
+
import cv2
|
6 |
+
import numpy as np
|
7 |
+
|
8 |
+
|
9 |
+
class SimpleVideoMerger:
|
10 |
+
def __init__(self, fps: int = 30):
|
11 |
+
self.fps = fps
|
12 |
+
|
13 |
+
def merge_videos(self, input_folder: str, output_filename: str):
|
14 |
+
video_files = [f for f in Path(input_folder).glob("*.mp4")]
|
15 |
+
|
16 |
+
if not video_files:
|
17 |
+
print("No video files found in the specified directory.")
|
18 |
+
return
|
19 |
+
|
20 |
+
videos = []
|
21 |
+
|
22 |
+
for video_file in video_files:
|
23 |
+
video = cv2.VideoCapture(str(video_file))
|
24 |
+
videos.append(video)
|
25 |
+
|
26 |
+
width = int(videos[0].get(cv2.CAP_PROP_FRAME_WIDTH))
|
27 |
+
height = int(videos[0].get(cv2.CAP_PROP_FRAME_HEIGHT))
|
28 |
+
|
29 |
+
fourcc = cv2.VideoWriter_fourcc(*'MP4V')
|
30 |
+
out = cv2.VideoWriter(output_filename, fourcc, self.fps, (width, height))
|
31 |
+
|
32 |
+
for i, video in enumerate(videos):
|
33 |
+
ret, frame = video.read()
|
34 |
+
|
35 |
+
while ret:
|
36 |
+
out.write(frame)
|
37 |
+
ret, frame = video.read()
|
38 |
+
|
39 |
+
video.release()
|
40 |
+
|
41 |
+
out.release()
|
42 |
+
|
43 |
+
print(f"Concatenated video saved to {output_filename}.")
|
44 |
+
|
45 |
+
if __name__ == '__main__':
|
46 |
+
# 使用例 (コメントアウトされています)
|
47 |
+
merger = SimpleVideoMerger()
|
48 |
+
input_folder_path = r"image\Echoes-of-Creation_Blurred_mov"
|
49 |
+
output_folder_path = f"{input_folder_path}_Final"
|
50 |
+
os.makedirs(output_folder_path, exist_ok=True)
|
51 |
+
output_video_path = os.path.join(output_folder_path, "concatenated_video.mp4")
|
52 |
+
merger.merge_videos(input_folder_path, output_video_path)
|
modules/Transition/VideoMergerWithSmoothTransition.py
ADDED
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image, ImageFilter
|
2 |
+
import random
|
3 |
+
import os
|
4 |
+
from pathlib import Path
|
5 |
+
import cv2
|
6 |
+
import numpy as np
|
7 |
+
|
8 |
+
class VideoMergerWithSmoothTransition:
|
9 |
+
def __init__(self, fps: int = 30, transition_duration: float = 0.5):
|
10 |
+
self.fps = fps
|
11 |
+
self.transition_duration = transition_duration
|
12 |
+
|
13 |
+
def merge_videos(self, input_folder: str, output_filename: str):
|
14 |
+
video_files = [f for f in Path(input_folder).glob("*.mp4")]
|
15 |
+
|
16 |
+
if not video_files:
|
17 |
+
print("No video files found in the specified directory.")
|
18 |
+
return
|
19 |
+
|
20 |
+
videos = []
|
21 |
+
|
22 |
+
for video_file in video_files:
|
23 |
+
video = cv2.VideoCapture(str(video_file))
|
24 |
+
videos.append(video)
|
25 |
+
|
26 |
+
width = int(videos[0].get(cv2.CAP_PROP_FRAME_WIDTH))
|
27 |
+
height = int(videos[0].get(cv2.CAP_PROP_FRAME_HEIGHT))
|
28 |
+
|
29 |
+
fourcc = cv2.VideoWriter_fourcc(*'MP4V')
|
30 |
+
out = cv2.VideoWriter(output_filename, fourcc, self.fps, (width, height))
|
31 |
+
|
32 |
+
transition_frames_count = int(self.fps * self.transition_duration)
|
33 |
+
|
34 |
+
for i, video in enumerate(videos):
|
35 |
+
ret, prev_frame = video.read()
|
36 |
+
|
37 |
+
while ret:
|
38 |
+
if i < len(videos) - 1 and not video.get(cv2.CAP_PROP_POS_FRAMES) < video.get(cv2.CAP_PROP_FRAME_COUNT) - transition_frames_count:
|
39 |
+
alpha = (video.get(cv2.CAP_PROP_POS_FRAMES) - (video.get(cv2.CAP_PROP_FRAME_COUNT) - transition_frames_count)) / transition_frames_count
|
40 |
+
ret_next, next_frame = videos[i + 1].read()
|
41 |
+
|
42 |
+
if ret_next:
|
43 |
+
blended_frame = cv2.addWeighted(prev_frame, 1 - alpha, next_frame, alpha, 0)
|
44 |
+
out.write(blended_frame)
|
45 |
+
ret, prev_frame = video.read()
|
46 |
+
continue
|
47 |
+
|
48 |
+
out.write(prev_frame)
|
49 |
+
ret, prev_frame = video.read()
|
50 |
+
|
51 |
+
video.release()
|
52 |
+
|
53 |
+
out.release()
|
54 |
+
|
55 |
+
print(f"Concatenated video saved to {output_filename}.")
|
56 |
+
|
57 |
+
if __name__ == '__main__':
|
58 |
+
# 使用例 (コメントアウトされています)
|
59 |
+
merger = VideoMergerWithSmoothTransition()
|
60 |
+
input_folder_path = r"image\Echoes-of-Creation_Blurred_mov"
|
61 |
+
output_folder_path = f"{input_folder_path}_Final"
|
62 |
+
os.makedirs(output_folder_path, exist_ok=True)
|
63 |
+
output_video_path = os.path.join(output_folder_path, "concatenated_video.mp4")
|
64 |
+
merger.merge_videos(input_folder_path, output_video_path)
|
modules/utils/image_frame_generator.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image, ImageFilter
|
2 |
+
import random
|
3 |
+
import os
|
4 |
+
from pathlib import Path
|
5 |
+
import cv2
|
6 |
+
import numpy as np
|
7 |
+
|
8 |
+
def generate_centered_moving_frames(input_path: str, crop_width: int = 1620, crop_height: int = 2880, min_frames_required: int = 120):
|
9 |
+
"""
|
10 |
+
画像を上から下に移動させて、指定されたサイズでクロップしたフレームを生成します。
|
11 |
+
クロップは画像の中心を基準に行います。
|
12 |
+
|
13 |
+
引数:
|
14 |
+
- input_path: 入力画像のパス
|
15 |
+
- crop_width: クロップする幅
|
16 |
+
- crop_height: クロップする高さ
|
17 |
+
|
18 |
+
戻り値:
|
19 |
+
- クロップされたフレームのリスト
|
20 |
+
"""
|
21 |
+
|
22 |
+
image = Image.open(input_path)
|
23 |
+
aspect_ratio = image.height / image.width
|
24 |
+
new_height_initial_resize = int(crop_width * aspect_ratio)
|
25 |
+
resized_initial = image.resize((crop_width, new_height_initial_resize))
|
26 |
+
|
27 |
+
resize_factor = random.uniform(1.2, 1.5)
|
28 |
+
new_width = int(resized_initial.width * resize_factor)
|
29 |
+
new_height = int(resized_initial.height * resize_factor)
|
30 |
+
resized_image = resized_initial.resize((new_width, new_height))
|
31 |
+
|
32 |
+
frames = []
|
33 |
+
max_y_offset = max(new_height - crop_height, 0)
|
34 |
+
|
35 |
+
# クロップの中心を維持するためのx_offsetを計算
|
36 |
+
x_offset = (new_width - crop_width) // 2
|
37 |
+
|
38 |
+
step = max_y_offset // min_frames_required
|
39 |
+
for y_offset in range(0, max_y_offset + 1, step):
|
40 |
+
frame = resized_image.crop((x_offset, y_offset, x_offset + crop_width, y_offset + crop_height))
|
41 |
+
frames.append(frame)
|
42 |
+
|
43 |
+
return frames
|
44 |
+
|
45 |
+
if __name__ == '__main__':
|
46 |
+
# フレームを生成する
|
47 |
+
frames_modified = generate_centered_moving_frames(r"image\Echoes-of-Creation_Blurred\00028-1365031933.png")
|
48 |
+
|
49 |
+
print(len(frames_modified))
|
modules/utils/v_image_blurred_utils.py
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image, ImageFilter
|
2 |
+
import random
|
3 |
+
import os
|
4 |
+
from pathlib import Path
|
5 |
+
import cv2
|
6 |
+
import numpy as np
|
7 |
+
|
8 |
+
def embed_image_on_blurred_background(input_path: str, output_path: str, height: int = 2000) -> None:
|
9 |
+
"""
|
10 |
+
入力画像をブラーしたバージョンの上に配置し、その結果を保存します。
|
11 |
+
|
12 |
+
引数:
|
13 |
+
- input_path: 入力画像のパス
|
14 |
+
- output_path: 処理された画像を保存する場所
|
15 |
+
- height: 出力画像の希望の高さ(デフォルトは2000ピクセル)
|
16 |
+
"""
|
17 |
+
|
18 |
+
# 与えられたパスから画像を読み込む
|
19 |
+
image = Image.open(input_path)
|
20 |
+
|
21 |
+
# 画像の元のサイズを取得する
|
22 |
+
original_width, original_height = image.size
|
23 |
+
|
24 |
+
# 9:16のアスペクト比と指定された高さに基づいて、出力画像の幅を計算する
|
25 |
+
target_width = int(height * 9 / 16)
|
26 |
+
|
27 |
+
# 元の画像のブラーしたバージョンを作成する
|
28 |
+
blurred_image = image.filter(ImageFilter.GaussianBlur(20))
|
29 |
+
|
30 |
+
# ブラー画像を希望の出力サイズにリサイズする
|
31 |
+
resized_blurred_background = blurred_image.resize((target_width, height))
|
32 |
+
|
33 |
+
# 元のアスペクト比を保持したまま、元の画像を指定された高さにリサイズする
|
34 |
+
new_width = int(original_width * (height / original_height))
|
35 |
+
resized_image_keep_aspect = image.resize((new_width, height), Image.ANTIALIAS)
|
36 |
+
|
37 |
+
# リサイズされた元の画像をブラーした背景の中央に配置する位置を計算する
|
38 |
+
x_offset = (resized_blurred_background.width - resized_image_keep_aspect.width) // 2
|
39 |
+
y_offset = (resized_blurred_background.height - resized_image_keep_aspect.height) // 2
|
40 |
+
|
41 |
+
# 画像に透明度がある場合(RGBAモード)、背景にペーストする際のマスクとして使用する
|
42 |
+
mask_keep_aspect = resized_image_keep_aspect if resized_image_keep_aspect.mode == "RGBA" else None
|
43 |
+
|
44 |
+
# リサイズされた元の画像をブラーした背景の上にオーバーレイする
|
45 |
+
resized_blurred_background.paste(resized_image_keep_aspect, (x_offset, y_offset), mask_keep_aspect)
|
46 |
+
|
47 |
+
# 指定されたパスに結合された画像を保存する
|
48 |
+
resized_blurred_background.save(output_path)
|
modules/video_merger.py
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image, ImageFilter
|
2 |
+
import random
|
3 |
+
import os
|
4 |
+
from pathlib import Path
|
5 |
+
import cv2
|
6 |
+
import numpy as np
|
7 |
+
|
8 |
+
from modules.Transition.VideoMergerWithSmoothTransition import VideoMergerWithSmoothTransition
|
9 |
+
|
10 |
+
if __name__ == '__main__':
|
11 |
+
# 使用例 (コメントアウトされています)
|
12 |
+
merger = VideoMergerWithSmoothTransition()
|
13 |
+
input_folder_path = r"image\Echoes-of-Creation_Blurred_mov"
|
14 |
+
output_folder_path = f"{input_folder_path}_Final"
|
15 |
+
os.makedirs(output_folder_path, exist_ok=True)
|
16 |
+
output_video_path = os.path.join(output_folder_path, "concatenated_video.mp4")
|
17 |
+
merger.merge_videos(input_folder_path, output_video_path)
|
requirements.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
gradio
|
2 |
+
opencv-python
|
3 |
+
Pillow==9.5.0
|