|
5 | 5 | # @Author :Zhangjinzhao
|
6 | 6 | # @Software :PyCharm
|
7 | 7 |
|
8 |
| -import mimetypes |
9 |
| -import os |
10 |
| -import subprocess |
11 | 8 | from pathlib import Path
|
12 | 9 |
|
13 |
| -import boto3 |
14 |
| -from botocore.exceptions import ClientError |
15 |
| - |
16 | 10 | from tool_utils.log_utils import RichLogger
|
17 | 11 |
|
18 | 12 | rich_logger = RichLogger()
|
19 | 13 |
|
20 | 14 |
|
21 |
| -class S3Utils: |
22 |
| - def __init__(self): |
23 |
| - s3endpoint = os.getenv('S3_ENDPOINT') # 请填入控制台 “Bucket 设置” 页面底部的 “Endpoint” 标签中的信息 |
24 |
| - s3region = os.getenv('S3_REGION') |
25 |
| - s3accessKeyId = os.getenv('S3_ACCESS_KEY') # 请到控制台创建子账户,并为子账户创建相应 accessKey |
26 |
| - s3SecretKeyId = os.getenv('S3_SECRET_KEY') # !!切记,创建子账户时,需要手动为其分配具体权限!! |
27 |
| - self.bucket = os.getenv('S3_BUCKET') # 请填入控制台 “Bucket 列表” 页面的 “Bucket 名称” |
28 |
| - self.s3_client = boto3.client( |
29 |
| - 's3', |
30 |
| - aws_access_key_id=s3accessKeyId, |
31 |
| - aws_secret_access_key=s3SecretKeyId, |
32 |
| - endpoint_url=s3endpoint, |
33 |
| - region_name=s3region |
34 |
| - ) |
35 |
| - |
36 |
| - @rich_logger |
37 |
| - def check_s3_file_exists(self, file_path): |
38 |
| - """ |
39 |
| - 检查文件在 S3 上是否已存在 |
40 |
| - :param file_path: 本地文件路径 |
41 |
| - :return: 存在返回 True,不存在返回 False |
42 |
| - """ |
43 |
| - try: |
44 |
| - # 将路径替换为统一的单斜杠 |
45 |
| - unified_path = file_path.replace("\\", "/") |
46 |
| - # 分割路径 |
47 |
| - parts = unified_path.split("/") |
48 |
| - xovideos_indices = [i for i, part in enumerate(parts) if part == "XOVideos"] |
49 |
| - |
50 |
| - videos_index = xovideos_indices[1] # 获取第二个 "XOVideos" 的索引 |
51 |
| - s3_key = "/".join(parts[videos_index:]) |
52 |
| - |
53 |
| - # 检查文件是否存在 |
54 |
| - try: |
55 |
| - self.s3_client.head_object(Bucket=self.bucket, Key=s3_key) |
56 |
| - rich_logger.info(f"文件已存在于 S3: {s3_key}") |
57 |
| - return True |
58 |
| - except ClientError as e: |
59 |
| - error_code = e.response['Error'].get('Code', 'Unknown') |
60 |
| - if error_code == '404': |
61 |
| - rich_logger.info(f"文件不存在于 S3: {s3_key}") |
62 |
| - return False |
63 |
| - else: |
64 |
| - rich_logger.error(f"检查 S3 文件时出错 ({error_code}): {e}") |
65 |
| - return False |
66 |
| - |
67 |
| - except Exception as e: |
68 |
| - rich_logger.error(f"检查 S3 文件时发生未知错误: {e}") |
69 |
| - return False |
70 |
| - |
71 |
| - @rich_logger |
72 |
| - def s4_upload_file(self, file_path, delete_on_success=True, delete_on_failure=False): |
73 |
| - |
74 |
| - try: |
75 |
| - # 路径合规性检查 |
76 |
| - if not os.path.isfile(file_path): |
77 |
| - rich_logger.error(f"文件不存在或不可读: {file_path}") |
78 |
| - return False |
79 |
| - |
80 |
| - # 将路径替换为统一的单斜杠 |
81 |
| - unified_path = file_path.replace("\\", "/") |
82 |
| - # 分割路径 |
83 |
| - parts = unified_path.split("/") |
84 |
| - xovideos_indices = [i for i, part in enumerate(parts) if part == "XOVideos"] |
85 |
| - |
86 |
| - videos_index = xovideos_indices[1] # 获取第二个 "XOVideos" 的索引 |
87 |
| - s3_key = "/".join(parts[videos_index:]) |
88 |
| - |
89 |
| - # 检查文件是否存在 |
90 |
| - try: |
91 |
| - self.s3_client.head_object(Bucket=self.bucket, Key=s3_key) |
92 |
| - rich_logger.info(f"S4文件已存在,跳过上传: {s3_key}") |
93 |
| - |
94 |
| - if delete_on_success: |
95 |
| - try: |
96 |
| - os.remove(file_path) |
97 |
| - except OSError as e: |
98 |
| - rich_logger.error(f"删除本地文件失败: {e}") |
99 |
| - return True |
100 |
| - except ClientError as e: |
101 |
| - error_code = e.response['Error'].get('Code', 'Unknown') |
102 |
| - if error_code != '404': |
103 |
| - raise |
104 |
| - |
105 |
| - # 动态检测 MIME 类型 |
106 |
| - content_type, _ = mimetypes.guess_type(file_path) |
107 |
| - extra_args = { |
108 |
| - "ContentType": content_type or "application/octet-stream", |
109 |
| - "ContentDisposition": "inline" |
110 |
| - } |
111 |
| - |
112 |
| - # 记录文件大小 |
113 |
| - file_size = os.path.getsize(file_path) |
114 |
| - size_units = ['B', 'KB', 'MB', 'GB', 'TB'] |
115 |
| - size_index = 0 |
116 |
| - while file_size >= 1024 and size_index < len(size_units) - 1: |
117 |
| - file_size /= 1024.0 |
118 |
| - size_index += 1 |
119 |
| - size_str = f"{int(file_size)} {size_units[size_index]}" if size_index == 0 else f"{file_size:.2f} {size_units[size_index]}".rstrip('0').rstrip('.') |
120 |
| - rich_logger.info(f"S4开始上传: {s3_key},大小: {size_str}") |
121 |
| - |
122 |
| - # 执行上传 |
123 |
| - self.s3_client.upload_file( |
124 |
| - Filename=file_path, |
125 |
| - Bucket=self.bucket, |
126 |
| - Key=s3_key, |
127 |
| - ExtraArgs=extra_args |
128 |
| - ) |
129 |
| - rich_logger.info(f"S4上传成功: {s3_key}") |
130 |
| - |
131 |
| - # 上传成功后删除本地文件 |
132 |
| - if delete_on_success: |
133 |
| - try: |
134 |
| - os.remove(file_path) |
135 |
| - except OSError as e: |
136 |
| - rich_logger.error(f"删除本地文件失败: {e}") |
137 |
| - return True |
138 |
| - |
139 |
| - except ClientError as e: |
140 |
| - error_code = e.response['Error'].get('Code', 'Unknown') |
141 |
| - rich_logger.error(f"S4上传失败 ({error_code}): {e}") |
142 |
| - if delete_on_failure: |
143 |
| - try: |
144 |
| - os.remove(file_path) |
145 |
| - except OSError as err: |
146 |
| - rich_logger.error(f"删除本地文件失败: {err}") |
147 |
| - return False |
148 |
| - except Exception as e: |
149 |
| - rich_logger.exception(f"S4未知错误: {e}") |
150 |
| - if delete_on_failure: |
151 |
| - try: |
152 |
| - os.remove(file_path) |
153 |
| - except OSError as err: |
154 |
| - rich_logger.error(f"删除本地文件失败: {err}") |
155 |
| - return False |
156 |
| - |
157 |
| - @staticmethod |
158 |
| - @rich_logger |
159 |
| - def ffmpeg_video_streaming(input_file): |
160 |
| - """ |
161 |
| - 使用FFmpeg将视频转换为H.264格式,并进行流优化处理,使其能够流式传输。 |
162 |
| -
|
163 |
| - 参数: |
164 |
| - - input_file: 输入的视频文件路径 |
165 |
| - - output_file: 输出的优化后的视频文件路径 |
166 |
| -
|
167 |
| - 返回: |
168 |
| - - 成功: 返回输出文件的路径 |
169 |
| - - 失败: 返回 None |
170 |
| - """ |
171 |
| - output_file = input_file.replace('.mp4', 'h264.mp4') |
172 |
| - command = [ |
173 |
| - 'ffmpeg', |
174 |
| - '-i', input_file, |
175 |
| - '-c:v', 'libx264', # 使用H.264编解码器 |
176 |
| - '-c:a', 'copy', # 保留原始音频 |
177 |
| - '-movflags', 'faststart', # 使视频文件头放在文件开始处,优化流媒体播放 |
178 |
| - output_file |
179 |
| - ] |
180 |
| - |
181 |
| - try: |
182 |
| - # 使用 subprocess.run 执行 FFmpeg 命令,并捕获 stdout 和 stderr 以便调试 |
183 |
| - subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
184 |
| - |
185 |
| - rich_logger.info(f"视频优化和H.264转换完成: {output_file}") |
186 |
| - os.remove(input_file) # 删除原视频,保留流式优化视频 |
187 |
| - os.rename(output_file, input_file) |
188 |
| - return input_file |
189 |
| - except subprocess.CalledProcessError as e: |
190 |
| - # 捕获 FFmpeg 命令的返回码和输出 |
191 |
| - rich_logger.exception(f"视频优化失败: {output_file}\n错误信息: {e.stderr.decode('utf-8')}") |
192 |
| - return None |
193 |
| - except Exception as e: |
194 |
| - # 捕获其他意外错误 |
195 |
| - rich_logger.error(f"未知错误: {e}") |
196 |
| - return None |
197 |
| - |
198 |
| - |
199 | 15 | class ProjectRootFinder:
|
200 | 16 | PROJECT_MARKERS = {
|
201 | 17 | "Python": ["setup.py", "requirements.txt", "pyproject.toml", "manage.py", "Pipfile", "app.py"],
|
|
0 commit comments