Я создаю веб-редактор видео, где пользователи могут: < /p>
Добавить несколько видео
Добавить изображения
Добавить текстовые наложения с цветом фона < /p >
Frontend отправляет координаты, где каждый элемент (x, y) представляет свою центральную позицию. Нажмите, я отправляю данные в бэкэнд, например-< /p>
данные внешнего интерфейса для каждого объекта, представляющего собой текстовое изображение и видео, которые мы сохраняем в виде массива объектов ниже, — это строка данных для каждого объекта —
< pre class="lang-js Prettyprint-override">
// the frontend data for each
const newVideo = {
id: uuidv4(),
src: URL.createObjectURL(videoData.videoBlob),
originalDuration: videoData.duration,
duration: videoData.duration,
startTime: 0,
playbackOffset: 0,
endTime: videoData.endTime || videoData.duration,
isPlaying: false,
isDragging: false,
speed: 1,
volume: 100,
x: window.innerHeight / 2,
y: window.innerHeight / 2,
width: videoData.width,
height: videoData.height,
};
const newTextObject = {
id: uuidv4(),
description: text,
opacity: 100,
x: containerWidth.width / 2,
y: containerWidth.height / 2,
fontSize: 18,
duration: 20,
endTime: 20,
startTime: 0,
color: "#ffffff",
backgroundColor: hasBG,
padding: 8,
fontWeight: "normal",
width: 200,
height: 40,
};
const newImage = {
id: uuidv4(),
src: URL.createObjectURL(imageData),
x: containerWidth.width / 2,
y: containerWidth.height / 2,
width: 200,
height: 200,
borderRadius: 0,
startTime: 0,
endTime: 20,
duration: 20,
opacity: 100,
};
< /code>
Бэкэнд-код-< /p>
import os
import shutil
import subprocess
from flask import Flask, request, send_file
import ffmpeg
import json
from werkzeug.utils import secure_filename
import uuid
from flask_cors import CORS
app = Flask(__name__)
CORS(app, resources={r"/*": {"origins": "*"}})
UPLOAD_FOLDER = 'temp_uploads'
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
@app.route('/')
def home():
return 'Hello World'
OUTPUT_WIDTH = 1920
OUTPUT_HEIGHT = 1080
@app.route('/process', methods=['POST'])
def process_video():
work_dir = None
try:
work_dir = os.path.abspath(os.path.join(UPLOAD_FOLDER, str(uuid.uuid4())))
os.makedirs(work_dir)
print(f"Created working directory: {work_dir}")
metadata = json.loads(request.form['metadata'])
print("Received metadata:", json.dumps(metadata, indent=2))
video_paths = []
videos = request.files.getlist('videos')
for idx, video in enumerate(videos):
filename = f"video_{idx}.mp4"
filepath = os.path.join(work_dir, filename)
video.save(filepath)
if os.path.exists(filepath) and os.path.getsize(filepath) > 0:
video_paths.append(filepath)
print(f"Saved video to: {filepath} Size: {os.path.getsize(filepath)}")
else:
raise Exception(f"Failed to save video {idx}")
image_paths = []
images = request.files.getlist('images')
for idx, image in enumerate(images):
filename = f"image_{idx}.png"
filepath = os.path.join(work_dir, filename)
image.save(filepath)
if os.path.exists(filepath):
image_paths.append(filepath)
print(f"Saved image to: {filepath}")
output_path = os.path.join(work_dir, 'output.mp4')
filter_parts = []
base_duration = metadata["videos"][0]["duration"] if metadata["videos"] else 10
filter_parts.append(f'color=c=black:s={OUTPUT_WIDTH}x{OUTPUT_HEIGHT}:d={base_duration}[canvas];')
for idx, (path, meta) in enumerate(zip(video_paths, metadata['videos'])):
x_pos = int(meta.get("x", 0) - (meta.get("width", 0) / 2))
y_pos = int(meta.get("y", 0) - (meta.get("height", 0) / 2))
filter_parts.extend([
f'[{idx}:v]setpts=PTS-STARTPTS,scale={meta.get("width", -1)}:{meta.get("height", -1)}[v{idx}];',
f'[{idx}:a]asetpts=PTS-STARTPTS[a{idx}];'
])
if idx == 0:
filter_parts.append(
f'[canvas][v{idx}]overlay=x={x_pos}:y={y_pos}:eval=init[temp{idx}];'
)
else:
filter_parts.append(
f'[temp{idx-1}][v{idx}]overlay=x={x_pos}:y={y_pos}:'
f'enable=\'between(t,{meta["startTime"]},{meta["endTime"]})\':eval=init'
f'[temp{idx}];'
)
last_video_temp = f'temp{len(video_paths)-1}'
if video_paths:
audio_mix_parts = []
for idx in range(len(video_paths)):
audio_mix_parts.append(f'[a{idx}]')
filter_parts.append(f'{"".join(audio_mix_parts)}amix=inputs={len(video_paths)}[aout];')
if image_paths:
for idx, (img_path, img_meta) in enumerate(zip(image_paths, metadata['images'])):
input_idx = len(video_paths) + idx
x_pos = int(img_meta["x"] - (img_meta["width"] / 2))
y_pos = int(img_meta["y"] - (img_meta["height"] / 2))
filter_parts.extend([
f'[{input_idx}:v]scale={img_meta["width"]}:{img_meta["height"]}[img{idx}];',
f'[{last_video_temp}][img{idx}]overlay=x={x_pos}:y={y_pos}:'
f'enable=\'between(t,{img_meta["startTime"]},{img_meta["endTime"]})\':'
f'alpha={img_meta["opacity"]/100}[imgout{idx}];'
])
last_video_temp = f'imgout{idx}'
if metadata.get('texts'):
for idx, text in enumerate(metadata['texts']):
next_output = f'text{idx}' if idx < len(metadata['texts']) - 1 else 'vout'
escaped_text = text["description"].replace("'", "\\'")
x_pos = int(text["x"] - (text["width"] / 2))
y_pos = int(text["y"] - (text["height"] / 2))
text_filter = (
f'[{last_video_temp}]drawtext=text=\'{escaped_text}\':'
f'x={x_pos}:y={y_pos}:'
f'fontsize={text["fontSize"]}:'
f'fontcolor={text["color"]}'
)
if text.get('backgroundColor'):
text_filter += f':box=1:boxcolor={text["backgroundColor"]}:boxborderw=5'
if text.get('fontWeight') == 'bold':
text_filter += ':font=Arial-Bold'
text_filter += (
f':enable=\'between(t,{text["startTime"]},{text["endTime"]})\''
f'[{next_output}];'
)
filter_parts.append(text_filter)
last_video_temp = next_output
else:
filter_parts.append(f'[{last_video_temp}]null[vout];')
filter_complex = ''.join(filter_parts)
cmd = [
'ffmpeg',
*sum([['-i', path] for path in video_paths], []),
*sum([['-i', path] for path in image_paths], []),
'-filter_complex', filter_complex,
'-map', '[vout]'
]
if video_paths:
cmd.extend(['-map', '[aout]'])
cmd.extend(['-y', output_path])
print(f"Running ffmpeg command: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"FFmpeg error output: {result.stderr}")
raise Exception(f"FFmpeg processing failed: {result.stderr}")
return send_file(
output_path,
mimetype='video/mp4',
as_attachment=True,
download_name='final_video.mp4'
)
except Exception as e:
print(f"Error in video processing: {str(e)}")
return {'error': str(e)}, 500
finally:
if work_dir and os.path.exists(work_dir):
try:
print(f"Directory contents before cleanup: {os.listdir(work_dir)}")
if not os.environ.get('FLASK_DEBUG'):
shutil.rmtree(work_dir)
else:
print(f"Keeping directory for debugging: {work_dir}")
except Exception as e:
print(f"Cleanup error: {str(e)}")
if __name__ == '__main__':
app.run(debug=True, port=8000)
Я также прикрепляю, как финальная версия выглядит на веб-интерфейсе по сравнению с загруженным видео
и, как вы можете видеть, в загруженном видео все координаты и позиции перепутаны. будь то тексты, изображения или видео
может кто-нибудь, пожалуйста, помогите мне разобраться в этом
Я создаю веб-редактор видео, где пользователи могут: < /p> Добавить несколько видео Добавить изображения Добавить текстовые наложения с цветом фона < /p > Frontend отправляет координаты, где каждый элемент (x, y) представляет свою центральную позицию. Нажмите, я отправляю данные в бэкэнд, например-< /p> [code] const exportAllVideos = async () => { try { const formData = new FormData();
video_paths = [] videos = request.files.getlist('videos') for idx, video in enumerate(videos): filename = f"video_{idx}.mp4" filepath = os.path.join(work_dir, filename) video.save(filepath) if os.path.exists(filepath) and os.path.getsize(filepath) > 0: video_paths.append(filepath) print(f"Saved video to: {filepath} Size: {os.path.getsize(filepath)}") else: raise Exception(f"Failed to save video {idx}")
image_paths = [] images = request.files.getlist('images') for idx, image in enumerate(images): filename = f"image_{idx}.png" filepath = os.path.join(work_dir, filename) image.save(filepath) if os.path.exists(filepath): image_paths.append(filepath) print(f"Saved image to: {filepath}")
if video_paths: audio_mix_parts = [] for idx in range(len(video_paths)): audio_mix_parts.append(f'[a{idx}]') filter_parts.append(f'{"".join(audio_mix_parts)}amix=inputs={len(video_paths)}[aout];')
if image_paths: for idx, (img_path, img_meta) in enumerate(zip(image_paths, metadata['images'])): input_idx = len(video_paths) + idx
except Exception as e: print(f"Error in video processing: {str(e)}") return {'error': str(e)}, 500
finally: if work_dir and os.path.exists(work_dir): try: print(f"Directory contents before cleanup: {os.listdir(work_dir)}") if not os.environ.get('FLASK_DEBUG'): shutil.rmtree(work_dir) else: print(f"Keeping directory for debugging: {work_dir}") except Exception as e: print(f"Cleanup error: {str(e)}")
if __name__ == '__main__': app.run(debug=True, port=8000)
[/code] Я также прикрепляю, как финальная версия выглядит на веб-интерфейсе по сравнению с загруженным видео и, как вы можете видеть, в загруженном видео все координаты и позиции перепутаны. будь то тексты, изображения или видео [img]https://i.sstatic.net/8BiEh6TK.png[/img] [img]https://i.sstatic.net/fzmPtjP6.png[/img] может кто-нибудь, пожалуйста, помогите мне разобраться в этом :)
Эффект перехода начинается с ВЕРХНЕГО ЛЕВОГО угла, но нужно, чтобы он начинался с левого угла.
По сути, я пытаюсь сделать его похожим на небольшую полосу загрузки, которая загружается при наведении курсора на блок.
У меня есть SVG на HTML-странице с динамической шириной, и я хочу нарисовать внутри него круговой текстовый путь, который будет оставаться по центру независимо от изменения размера окна браузера.
У меня не так много опыта в Python, но я совершенствуюсь благодаря этому сообществу! Мне отчаянно нужна функция, которая принимает входные данные и выдает следующий результат:
Ввод :
1- Latitude/ координаты долготы центра круга 1 (например,...
У меня проблема с размещением наложения поверх Text-Image-Section. Я попытался визуализировать ожидаемый результат на прикрепленном изображении. Наложение (синее) должно располагаться сверху, не перекрывая текст. Все тексты (в Text-Image-Section и в...
У меня проблема с размещением наложения поверх Text-Image-Section. Я попытался визуализировать ожидаемый результат на прикрепленном изображении. Наложение (синее) должно располагаться сверху, не перекрывая текст. Все тексты (в Text-Image-Section и в...