congcuong-cse commited on
Commit
34f063d
·
2 Parent(s): c4568cc fec84c5

Merge branch 'main' of hf.co:spaces/longtrinhquang/TTS-Talker

Browse files
.dockerignore CHANGED
@@ -1,3 +1,5 @@
1
  .python-version
2
  .backup
3
  backup
 
 
 
1
  .python-version
2
  .backup
3
  backup
4
+ results/
5
+ tts_cache/
Dockerfile CHANGED
@@ -26,6 +26,7 @@ RUN apt-get update && \
26
  libxmlsec1-dev \
27
  libffi-dev \
28
  liblzma-dev && \
 
29
  apt-get clean && \
30
  rm -rf /var/lib/apt/lists/*
31
 
@@ -48,6 +49,7 @@ COPY --chown=1000 requirements.txt /tmp/requirements.txt
48
  RUN pip install --no-cache-dir -U -r /tmp/requirements.txt
49
 
50
  COPY --chown=1000 . ${HOME}/app
 
51
  RUN ls -a
52
  ENV PYTHONPATH=${HOME}/app \
53
  PYTHONUNBUFFERED=1 \
@@ -56,4 +58,7 @@ ENV PYTHONPATH=${HOME}/app \
56
  GRADIO_SERVER_NAME=0.0.0.0 \
57
  GRADIO_THEME=huggingface \
58
  SYSTEM=spaces
 
 
 
59
  CMD ["python", "app.py"]
 
26
  libxmlsec1-dev \
27
  libffi-dev \
28
  liblzma-dev && \
29
+ apt-get install -y cron && \
30
  apt-get clean && \
31
  rm -rf /var/lib/apt/lists/*
32
 
 
49
  RUN pip install --no-cache-dir -U -r /tmp/requirements.txt
50
 
51
  COPY --chown=1000 . ${HOME}/app
52
+ RUN chmod +x ${HOME}/app/utils/clear_results.sh ${HOME}/app/utils/entrypoint.sh
53
  RUN ls -a
54
  ENV PYTHONPATH=${HOME}/app \
55
  PYTHONUNBUFFERED=1 \
 
58
  GRADIO_SERVER_NAME=0.0.0.0 \
59
  GRADIO_THEME=huggingface \
60
  SYSTEM=spaces
61
+
62
+ USER root
63
+ ENTRYPOINT ["/home/user/app/utils/entrypoint.sh"]
64
  CMD ["python", "app.py"]
README.md CHANGED
@@ -1,8 +1,8 @@
1
  ---
2
- title: SadTalker
3
  emoji: 😭
4
  colorFrom: purple
5
- colorTo: green
6
  sdk: gradio
7
  sdk_version: 5.45.0
8
  python_version: 3.10.18
@@ -11,39 +11,94 @@ pinned: false
11
  license: mit
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
15
 
16
- Use local:
17
- python=3.10
18
 
19
- ```
20
- pip install torch==2.4.0+cu124 torchaudio==2.4.0+cu124 torchvision==0.19.0 --extra-index-url https://download.pytorch.org/whl/cu124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  ```
22
 
 
 
 
 
23
  ```
 
 
 
 
24
  sudo apt-get update
25
  sudo apt-get install sox ffmpeg
26
  ```
27
 
28
- ## Backup and Restore Volume
 
 
29
 
30
- p/s: Need to mount backup folder to container
31
 
32
- ### Backup
33
 
34
  ```bash
 
35
  tar -czvf /backup/data_cache_backup.tar.gz /home/user/.cache
 
 
36
  tar -czvf /backup/data_gfpgan_backup.tar.gz /home/user/app/gfpgan
37
  ```
38
 
39
- ### Restore
40
 
41
  ```bash
 
42
  mkdir -p /home/user/.cache
43
  cd /home/user/.cache
44
  tar -xzvf /backup/data_cache_backup.tar.gz --strip 1
45
 
 
46
  mkdir -p /home/user/app/gfpgan
47
  cd /home/user/app/gfpgan
48
  tar -xzvf /backup/data_gfpgan_backup.tar.gz --strip 1
49
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Atalink-TTS-Talker
3
  emoji: 😭
4
  colorFrom: purple
5
+ colorTo: blue
6
  sdk: gradio
7
  sdk_version: 5.45.0
8
  python_version: 3.10.18
 
11
  license: mit
12
  ---
13
 
14
+ # 😭 Atalink-TTS-Talker
15
 
16
+ A Hugging Face Space powered by **Gradio**.
17
+ This project demonstrates **SadTalker** with local environment setup, backup/restore guides, and Docker deployment.
18
 
19
+
20
+ ## Reference: [Spaces Config Docs](https://huggingface.co/docs/hub/spaces-config-reference)
21
+
22
+ ---
23
+
24
+ ### Use local:
25
+
26
+ ## 🖥️ Local Setup
27
+
28
+ ### 1. Python environment
29
+
30
+ - Use **Python 3.10**
31
+
32
+ ```bash
33
+ python -m venv .venv
34
+ source .venv/bin/activate
35
  ```
36
 
37
+ ### 2. Install PyTorch with CUDA 12.4
38
+
39
+ ```bash
40
+ pip install torch==2.4.0+cu124 torchaudio==2.4.0+cu124 torchvision==0.19.0 --extra-index-url https://download.pytorch.org/whl/cu124
41
  ```
42
+
43
+ ### 3. Install dependencies
44
+
45
+ ```bash
46
  sudo apt-get update
47
  sudo apt-get install sox ffmpeg
48
  ```
49
 
50
+ ---
51
+
52
+ ## 💾 Backup & Restore Volumes
53
 
54
+ > ⚠️ Make sure you **mount the backup folder** into the container before running these commands.
55
 
56
+ ### 🔹 Backup
57
 
58
  ```bash
59
+ # Cache
60
  tar -czvf /backup/data_cache_backup.tar.gz /home/user/.cache
61
+
62
+ # GFPGAN data
63
  tar -czvf /backup/data_gfpgan_backup.tar.gz /home/user/app/gfpgan
64
  ```
65
 
66
+ ### 🔹 Restore
67
 
68
  ```bash
69
+ # Restore cache
70
  mkdir -p /home/user/.cache
71
  cd /home/user/.cache
72
  tar -xzvf /backup/data_cache_backup.tar.gz --strip 1
73
 
74
+ # Restore GFPGAN
75
  mkdir -p /home/user/app/gfpgan
76
  cd /home/user/app/gfpgan
77
  tar -xzvf /backup/data_gfpgan_backup.tar.gz --strip 1
78
  ```
79
+
80
+ ---
81
+
82
+ ## 🚀 Running the App
83
+
84
+ 1. Create and activate Python 3.10 environment
85
+ 2. Prepare environment:
86
+
87
+ ```bash
88
+ python utils/prepare_environment.py
89
+ ```
90
+
91
+ 3. Start with Docker Compose:
92
+
93
+ ```bash
94
+ docker compose up -d
95
+ ```
96
+
97
+ 4. If you change code, rebuild:
98
+ ```bash
99
+ docker compose up -d --build
100
+ ```
101
+
102
+ ---
103
+
104
+ ✨ Done! You can now run **SadTalker** locally or deploy with Docker.
app.py CHANGED
@@ -46,7 +46,8 @@ def download_model():
46
 
47
  def list_videos():
48
  # Lấy danh sách tất cả file mp4 trong results
49
- video_files = glob.glob("results/**/*.mp4", recursive=True)
 
50
  # Trả về danh sách file (có thể sort theo thời gian)
51
  return sorted(video_files, reverse=True)
52
 
@@ -77,23 +78,27 @@ def generate_voice_and_video(
77
  blink_every,
78
  ):
79
  import gradio as gr
 
80
  # Bắt đầu: Hiển thị trạng thái đang tạo audio
81
  yield (
82
  gr.update(value=None, visible=True, interactive=False),
83
  gr.update(value=None, visible=True, interactive=False),
84
- gr.update(value="⏳ Đang tạo âm thanh...", visible=True)
 
85
  )
86
 
87
  # 1. Sinh audio từ TTS
88
  (final_sample_rate, final_wave), _ = infer_tts(ref_audio, ref_text, gen_text, speed)
89
  tmp_audio = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
90
  import soundfile as sf
 
91
  sf.write(tmp_audio.name, final_wave, final_sample_rate)
92
  # Audio xong, chuyển sang tạo video
93
  yield (
94
  gr.update(value=tmp_audio.name, visible=True, interactive=True),
95
  gr.update(value=None, visible=True, interactive=False),
96
- gr.update(value="⏳ Đang tạo video...", visible=True)
 
97
  )
98
 
99
  # 2. Gọi SadTalker với audio vừa sinh ra
@@ -120,7 +125,8 @@ def generate_voice_and_video(
120
  yield (
121
  gr.update(value=tmp_audio.name, visible=True, interactive=True),
122
  gr.update(value=video_path, visible=True, interactive=True),
123
- gr.update(value="✅ Hoàn thành!", visible=True)
 
124
  )
125
  def list_files(directory):
126
  try:
@@ -234,7 +240,12 @@ def sadtalker_demo():
234
  gen_video = gr.Video(
235
  label="Video đã tạo", format="mp4", scale=1, width=180
236
  )
237
- status_box = gr.Textbox(label="Trạng thái tiến trình", interactive=False, value="", visible=True)
 
 
 
 
 
238
 
239
  def enable_generate(audio, text, image):
240
  return gr.update(interactive=bool(audio and text and image))
@@ -249,36 +260,12 @@ def sadtalker_demo():
249
  enable_generate, [ref_audio, gen_text, source_image], btn_generate
250
  )
251
 
252
- btn_generate.click(
253
- generate_voice_and_video,
254
- inputs=[
255
- ref_audio,
256
- ref_text,
257
- gen_text,
258
- speed,
259
- source_image,
260
- preprocess_type,
261
- is_still_mode,
262
- enhancer,
263
- batch_size,
264
- size_of_image,
265
- pose_style,
266
- facerender,
267
- exp_weight,
268
- use_ref_video,
269
- ref_video,
270
- ref_info,
271
- use_idle_mode,
272
- length_of_audio,
273
- blink_every,
274
- ],
275
- outputs=[output_audio, gen_video, status_box],
276
- )
277
  with gr.Tab("Lịch sử video"):
278
  with gr.Row(elem_classes="gr-row"):
279
  refresh_btn = gr.Button("🔄 Refresh File List")
280
 
281
  video_list = gr.Dropdown(
 
282
  choices=list_videos(),
283
  label="Chọn video để xem",
284
  interactive=True,
@@ -296,6 +283,31 @@ def sadtalker_demo():
296
 
297
  directory_input.change(fn=list_files, inputs=directory_input, outputs=file_list_output)
298
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  return sadtalker_interface
300
 
301
 
 
46
 
47
  def list_videos():
48
  # Lấy danh sách tất cả file mp4 trong results
49
+ PATH_RESULTS = "results"
50
+ video_files = glob.glob(f"{PATH_RESULTS}/**/*.mp4", recursive=True)
51
  # Trả về danh sách file (có thể sort theo thời gian)
52
  return sorted(video_files, reverse=True)
53
 
 
78
  blink_every,
79
  ):
80
  import gradio as gr
81
+
82
  # Bắt đầu: Hiển thị trạng thái đang tạo audio
83
  yield (
84
  gr.update(value=None, visible=True, interactive=False),
85
  gr.update(value=None, visible=True, interactive=False),
86
+ gr.update(value="⏳ Đang tạo âm thanh...", visible=True),
87
+ gr.update(choices=list_videos()),
88
  )
89
 
90
  # 1. Sinh audio từ TTS
91
  (final_sample_rate, final_wave), _ = infer_tts(ref_audio, ref_text, gen_text, speed)
92
  tmp_audio = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
93
  import soundfile as sf
94
+
95
  sf.write(tmp_audio.name, final_wave, final_sample_rate)
96
  # Audio xong, chuyển sang tạo video
97
  yield (
98
  gr.update(value=tmp_audio.name, visible=True, interactive=True),
99
  gr.update(value=None, visible=True, interactive=False),
100
+ gr.update(value="⏳ Đang tạo video...", visible=True),
101
+ gr.update(choices=list_videos()),
102
  )
103
 
104
  # 2. Gọi SadTalker với audio vừa sinh ra
 
125
  yield (
126
  gr.update(value=tmp_audio.name, visible=True, interactive=True),
127
  gr.update(value=video_path, visible=True, interactive=True),
128
+ gr.update(value="✅ Hoàn thành!", visible=True),
129
+ gr.update(choices=list_videos(), value=video_path),
130
  )
131
  def list_files(directory):
132
  try:
 
240
  gen_video = gr.Video(
241
  label="Video đã tạo", format="mp4", scale=1, width=180
242
  )
243
+ status_box = gr.Textbox(
244
+ label="Trạng thái tiến trình",
245
+ interactive=False,
246
+ value="",
247
+ visible=True,
248
+ )
249
 
250
  def enable_generate(audio, text, image):
251
  return gr.update(interactive=bool(audio and text and image))
 
260
  enable_generate, [ref_audio, gen_text, source_image], btn_generate
261
  )
262
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  with gr.Tab("Lịch sử video"):
264
  with gr.Row(elem_classes="gr-row"):
265
  refresh_btn = gr.Button("🔄 Refresh File List")
266
 
267
  video_list = gr.Dropdown(
268
+ value=list_videos()[0] if len(list_videos()) > 0 else None,
269
  choices=list_videos(),
270
  label="Chọn video để xem",
271
  interactive=True,
 
283
 
284
  directory_input.change(fn=list_files, inputs=directory_input, outputs=file_list_output)
285
 
286
+ btn_generate.click(
287
+ generate_voice_and_video,
288
+ inputs=[
289
+ ref_audio,
290
+ ref_text,
291
+ gen_text,
292
+ speed,
293
+ source_image,
294
+ preprocess_type,
295
+ is_still_mode,
296
+ enhancer,
297
+ batch_size,
298
+ size_of_image,
299
+ pose_style,
300
+ facerender,
301
+ exp_weight,
302
+ use_ref_video,
303
+ ref_video,
304
+ ref_info,
305
+ use_idle_mode,
306
+ length_of_audio,
307
+ blink_every,
308
+ ],
309
+ outputs=[output_audio, gen_video, status_box, video_list],
310
+ )
311
  return sadtalker_interface
312
 
313
 
app_tts.py CHANGED
@@ -34,6 +34,7 @@ from pathlib import Path
34
  from omegaconf import OmegaConf
35
  from datetime import datetime
36
  import hashlib
 
37
  # Retrieve token from secrets
38
  hf_token = os.getenv("HUGGINGFACEHUB_API_TOKEN")
39
 
@@ -173,7 +174,8 @@ def infer_tts(ref_audio_orig: str, ref_text_input: str, gen_text: str, speed: fl
173
  try:
174
  # Nếu người dùng nhập ref_text thì dùng, không thì để rỗng để tự động nhận diện
175
  ref_audio, ref_text = preprocess_ref_audio_text(ref_audio_orig, ref_text_input or "")
176
- gen_text_ = gen_text.strip()
 
177
  # --- BẮT ĐẦU: Thêm logic cache ---
178
  cache_path = get_audio_cache_path(gen_text_, ref_audio_orig, model)
179
  import soundfile as sf
@@ -183,7 +185,7 @@ def infer_tts(ref_audio_orig: str, ref_text_input: str, gen_text: str, speed: fl
183
  spectrogram = None
184
  else:
185
  final_wave, final_sample_rate, spectrogram = infer_process(
186
- ref_audio, ref_text.lower(), gen_text_, ema_model, vocoder, speed=speed
187
  )
188
  print(f"[CACHE] Saved new audio to: {cache_path}")
189
  sf.write(cache_path, final_wave, final_sample_rate)
 
34
  from omegaconf import OmegaConf
35
  from datetime import datetime
36
  import hashlib
37
+ import unicodedata
38
  # Retrieve token from secrets
39
  hf_token = os.getenv("HUGGINGFACEHUB_API_TOKEN")
40
 
 
174
  try:
175
  # Nếu người dùng nhập ref_text thì dùng, không thì để rỗng để tự động nhận diện
176
  ref_audio, ref_text = preprocess_ref_audio_text(ref_audio_orig, ref_text_input or "")
177
+ ref_text = unicodedata.normalize("NFC", ref_text.strip())
178
+ gen_text_ = unicodedata.normalize("NFC", gen_text.strip())
179
  # --- BẮT ĐẦU: Thêm logic cache ---
180
  cache_path = get_audio_cache_path(gen_text_, ref_audio_orig, model)
181
  import soundfile as sf
 
185
  spectrogram = None
186
  else:
187
  final_wave, final_sample_rate, spectrogram = infer_process(
188
+ ref_audio, ref_text, gen_text_, ema_model, vocoder, speed=speed
189
  )
190
  print(f"[CACHE] Saved new audio to: {cache_path}")
191
  sf.write(cache_path, final_wave, final_sample_rate)
docker-compose.yaml CHANGED
@@ -1,20 +1,25 @@
1
  services:
2
- test:
3
  build: .
4
  ports:
5
- - "7860:7860"
6
- stdin_open: true # equivalent to -it
7
- tty: true # equivalent to -it
8
- restart: "no" # equivalent to --rm (don’t restart container automatically)
9
- # environment:
10
- # - HF_ENDPOINT=http://172.16.15.118:9557/repository/atalink-hf-models
 
11
  volumes:
12
- - test_data_cache:/home/user/.cache
13
- - test_data_gfpgan:/home/user/app/gfpgan
14
- # - ./backup:/backup
15
-
 
 
 
 
16
  volumes:
17
- test_data_cache:
18
  external: true
19
- test_data_gfpgan:
20
  external: true
 
1
  services:
2
+ atalink-tts-talker:
3
  build: .
4
  ports:
5
+ - '7860:7860'
6
+ stdin_open: true # equivalent to -it
7
+ tty: true # equivalent to -it
8
+ restart: 'no' # equivalent to --rm (don’t restart container automatically)
9
+ environment:
10
+ - PATH_RESULTS= /app/results
11
+ # - HF_ENDPOINT=http://172.16.15.118:9557/repository/atalink-hf-models
12
  volumes:
13
+ - atalink_data_cache:/home/user/.cache
14
+ - atalink_data_gfpgan:/home/user/app/gfpgan
15
+ # # - ./backup:/backup
16
+ deploy:
17
+ resources:
18
+ reservations:
19
+ devices:
20
+ - capabilities: [gpu]
21
  volumes:
22
+ atalink_data_cache:
23
  external: true
24
+ atalink_data_gfpgan:
25
  external: true
src/generate_batch.py CHANGED
@@ -77,8 +77,18 @@ def get_data(first_coeff_path, audio_path, device, ref_eyeblink_coeff_path, stil
77
  m = spec[seq, :]
78
  indiv_mels.append(m.T)
79
  indiv_mels = np.asarray(indiv_mels) # T 80 16
 
 
 
 
 
 
 
 
 
 
80
 
81
- ratio = generate_blink_seq_randomly(num_frames) # T
82
  source_semantics_path = first_coeff_path
83
  source_semantics_dict = scio.loadmat(source_semantics_path)
84
  ref_coeff = source_semantics_dict['coeff_3dmm'][:1,:70] #1 70
@@ -93,7 +103,8 @@ def get_data(first_coeff_path, audio_path, device, ref_eyeblink_coeff_path, stil
93
  div = num_frames//refeyeblink_num_frames
94
  re = num_frames%refeyeblink_num_frames
95
  refeyeblink_coeff_list = [refeyeblink_coeff for i in range(div)]
96
- refeyeblink_coeff_list.append(refeyeblink_coeff[:re, :64])
 
97
  refeyeblink_coeff = np.concatenate(refeyeblink_coeff_list, axis=0)
98
  print(refeyeblink_coeff.shape[0])
99
 
 
77
  m = spec[seq, :]
78
  indiv_mels.append(m.T)
79
  indiv_mels = np.asarray(indiv_mels) # T 80 16
80
+ if num_frames < 20:
81
+ print(f"[WARN] num_frames={num_frames} too small, enable still_mode / skip blink.")
82
+ still = True
83
+ use_blink = False
84
+
85
+ # Blink ratio
86
+ if use_blink and not still:
87
+ ratio = generate_blink_seq_randomly(num_frames) # T × 1
88
+ else:
89
+ ratio = np.zeros((num_frames, 1)) # không blink
90
 
91
+ # ratio = generate_blink_seq_randomly(num_frames) # T
92
  source_semantics_path = first_coeff_path
93
  source_semantics_dict = scio.loadmat(source_semantics_path)
94
  ref_coeff = source_semantics_dict['coeff_3dmm'][:1,:70] #1 70
 
103
  div = num_frames//refeyeblink_num_frames
104
  re = num_frames%refeyeblink_num_frames
105
  refeyeblink_coeff_list = [refeyeblink_coeff for i in range(div)]
106
+ if re > 0:
107
+ refeyeblink_coeff_list.append(refeyeblink_coeff[:re, :64])
108
  refeyeblink_coeff = np.concatenate(refeyeblink_coeff_list, axis=0)
109
  print(refeyeblink_coeff.shape[0])
110
 
utils/clear_results.sh ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ # Xóa toàn bộ dữ liệu trong thư mục /app/results vào 10h tối mỗi ngày
3
+ # Thêm dòng sau vào crontab khi build docker
4
+ # 0 22 * * * /app/clear_results.sh
5
+
6
+ rm -rf /app/results/*
7
+ echo "[CRON] Đã xóa dữ liệu trong /app/results lúc $(date)" >> /app/clear_results.log
8
+
9
+ rm -rf /app/tts_cache/*
10
+ echo "[CRON] Đã xóa dữ liệu trong /app/tts_cache lúc $(date)" >> /app/clear_tts_cache.log
utils/entrypoint.sh ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ set -e
3
+ # Đảm bảo /var/run tồn tại và user có quyền ghi
4
+ mkdir -p /var/run
5
+ chmod 777 /var/run
6
+ # Cài crontab với job xóa results
7
+ crontab -l 2>/dev/null | { cat; echo "0 22 * * * /home/user/app/utils/clear_results.sh"; } | crontab -
8
+ # Start cron background
9
+ cron
10
+ # Start app.py (foreground)
11
+ exec "$@"
utils/prepare_environment.py CHANGED
@@ -23,8 +23,8 @@ DOWNLOADS = [
23
  ]
24
 
25
 
26
- TMP_DIR = "/tmp"
27
- VOLUME_PREFIX = "test_"
28
 
29
 
30
  def download_file(url, dest_folder):
@@ -62,11 +62,11 @@ def create_volume_and_extract(tar_path, volume_name):
62
  "-v",
63
  f"{volume_name}:/data", # Mount Docker volume (volume_name) vào thư mục /data trong container
64
  "-v",
65
- f"{os.path.dirname(tar_path)}:/tmpdata", # Mount thư mục chứa file tar.gz trên host vào /tmpdata trong container
66
  "busybox", # Image dùng để chạy container (ở đây là Ubuntu 22.04)
67
- "bash",
68
  "-c", # Chạy lệnh bash trong container
69
- f"tar -xzf /tmpdata/{os.path.basename(tar_path)} -C /data", # Lệnh giải nén file tar.gz từ /tmpdata vào /data
70
  ],
71
  check=True,
72
  )
@@ -91,10 +91,19 @@ def main():
91
  VOLUME_PREFIX
92
  + os.path.splitext(os.path.splitext(os.path.basename(tar_path))[0])[0]
93
  )
94
- print(f"🚀 [VOLUME] Name: \033[1;33m{volume_name}\033[0m")
95
- # create_volume_and_extract(tar_path, volume_name)
96
- # cleanup_tmp(downloaded_files)
97
 
98
 
99
  if __name__ == "__main__":
100
  main()
 
 
 
 
 
 
 
 
 
 
23
  ]
24
 
25
 
26
+ TMP_DIR = "tmp"
27
+ VOLUME_PREFIX = "atalink_"
28
 
29
 
30
  def download_file(url, dest_folder):
 
62
  "-v",
63
  f"{volume_name}:/data", # Mount Docker volume (volume_name) vào thư mục /data trong container
64
  "-v",
65
+ f"{os.path.abspath(os.path.dirname(tar_path))}:/tmpdata", # Mount thư mục chứa file tar.gz trên host vào /tmpdata trong container
66
  "busybox", # Image dùng để chạy container (ở đây là Ubuntu 22.04)
67
+ "sh",
68
  "-c", # Chạy lệnh bash trong container
69
+ f"tar -xzvf /tmpdata/{os.path.basename(tar_path)} --strip 1 -C /data", # Lệnh giải nén file tar.gz từ /tmpdata vào /data
70
  ],
71
  check=True,
72
  )
 
91
  VOLUME_PREFIX
92
  + os.path.splitext(os.path.splitext(os.path.basename(tar_path))[0])[0]
93
  )
94
+ print(f"🚀 [VOLUME] Name: \033[1;33m{volume_name}\033[0m")
95
+ create_volume_and_extract(tar_path, volume_name)
96
+ cleanup_tmp(downloaded_files)
97
 
98
 
99
  if __name__ == "__main__":
100
  main()
101
+
102
+
103
+ # if __name__ == "__main__":
104
+ # Test create_volume_and_extract với file test_data_backup.tar.gz
105
+ # test_tar = os.path.join("tmp", "data_backup.tar.gz")
106
+ # if os.path.exists(test_tar):
107
+ # create_volume_and_extract(test_tar, "atalink_data_backup")
108
+ # else:
109
+ # print("File tmp/test_data_backup.tar.gz không tồn tại để test.")