Improved the video file clip function
This commit is contained in:
@@ -592,7 +592,7 @@ async def upload_files(
|
||||
|
||||
|
||||
# ### BEGIN ### API Hosted File Route ### upload_files_fake() ###
|
||||
# This just needs to return the currect model for a hosted_file
|
||||
# This just needs to return the current model for a hosted_file
|
||||
# Everything else seems to be working well
|
||||
# Should this also do something with meta data and updating the DB?
|
||||
@router.post('/upload_files/fake')
|
||||
@@ -1256,14 +1256,14 @@ def run_ffmpeg(cmd):
|
||||
|
||||
@router.post('/create_video')
|
||||
async def create_video(
|
||||
file: UploadFile = File(...),
|
||||
title_image: UploadFile = File(None),
|
||||
title_part_1: str = Form(...),
|
||||
title_part_2: str = Form(...),
|
||||
subtitle_part_1: str = Form(...),
|
||||
subtitle_part_2: str = Form(...),
|
||||
font_color: str = Form('darkblue'),
|
||||
):
|
||||
file: UploadFile = File(...),
|
||||
title_image: UploadFile = File(None),
|
||||
title_part_1: str = Form(...),
|
||||
title_part_2: str = Form(...),
|
||||
subtitle_part_1: str = Form(...),
|
||||
subtitle_part_2: str = Form(...),
|
||||
font_color: str = Form('darkblue'),
|
||||
):
|
||||
log.setLevel(logging.DEBUG)
|
||||
log.debug(locals())
|
||||
|
||||
@@ -1359,9 +1359,163 @@ async def create_video(
|
||||
return FileResponse(video_name, media_type='video/mp4', filename=f'{title_part_1}_{subtitle_part_1}.mp4')
|
||||
|
||||
|
||||
|
||||
@router.post('/clip_video')
|
||||
# ### BEGIN ### API Hosted File ### clip_video() ###
|
||||
# Updated 2025-01-07
|
||||
@router.get('/{hosted_file_id}/clip_video')
|
||||
async def clip_video(
|
||||
hosted_file_id: str = Path(min_length=11, max_length=22),
|
||||
|
||||
link_to_type: str = Query(..., min_length=2, max_length=50),
|
||||
link_to_id: str = Query(..., min_length=11, max_length=22),
|
||||
|
||||
filename_no_ext: str = Query('automated_hosted_file_clip_video', min_length=1, max_length=240), # Intentionally below 255 characters to account for the extension
|
||||
|
||||
from_type: str = 'mp4',
|
||||
to_type: str = 'mp4',
|
||||
|
||||
start_time: str = Query(..., min_length=8, max_length=8),
|
||||
end_time: str = Query(..., min_length=8, max_length=8),
|
||||
reencode: bool = Query(False),
|
||||
|
||||
commons: Common_Route_Params = Depends(common_route_params),
|
||||
):
|
||||
log.setLevel(logging.DEBUG)
|
||||
log.debug(locals())
|
||||
|
||||
account_id = commons.x_account_id
|
||||
|
||||
if link_to_id := redis_lookup_id_random(record_id_random=link_to_id, table_name=link_to_type): pass
|
||||
else:
|
||||
return mk_resp(data=None, status_code=400, response=commons.response)
|
||||
|
||||
# Need to look up file_hash for hosted_file_id
|
||||
hosted_file_obj = load_hosted_file_obj(hosted_file_id=hosted_file_id)
|
||||
file_hash = hosted_file_obj.hash_sha256
|
||||
|
||||
file_hash_filename = f'{file_hash}.file'
|
||||
|
||||
hosted_files_path = settings.FILES_PATH['hosted_files_root']
|
||||
log.info(f'Hosted Files Path: {hosted_files_path}')
|
||||
log.debug(shutil.disk_usage(hosted_files_path))
|
||||
|
||||
file_subdirectory = file_hash[0:2]
|
||||
full_file_path = os.path.join(hosted_files_path, file_subdirectory, file_hash_filename)
|
||||
log.info(f'File Hash with Subdirectory: {full_file_path}')
|
||||
|
||||
hosted_tmp_path = settings.FILES_PATH['hosted_tmp_root']
|
||||
log.info(f'Hosted Tmp Path: {hosted_tmp_path}')
|
||||
log.debug(shutil.disk_usage(hosted_tmp_path))
|
||||
|
||||
hosted_tmp_convert_file_path = os.path.join(hosted_tmp_path, 'convert_file')
|
||||
if pathlib.Path(hosted_tmp_convert_file_path):
|
||||
log.info('Hosted tmp convert file path found')
|
||||
else:
|
||||
log.info('Creating hosted tmp convert file path')
|
||||
pathlib.Path(hosted_tmp_convert_file_path).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Run the ffmpeg command to clip a video file based on the start and end times given
|
||||
log.info('Run the ffmpeg command to clip a video file based on the start and end times given')
|
||||
|
||||
hosted_file_dict = None
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_video_file_clip:
|
||||
tmp_video_file_clip_path = tmp_video_file_clip.name
|
||||
|
||||
# NOTE: It seems very important that the -y argument is used with ffmpeg run by subprocess.run(). Otherwise the process will hang.
|
||||
# NOTE: This is a blocking process. It will take a while to complete.
|
||||
if reencode:
|
||||
new_video_file_clip_filename = f'{filename_no_ext}_[clip_reencode].{to_type}'
|
||||
log.debug(new_video_file_clip_filename)
|
||||
|
||||
cmd = f"ffmpeg -hide_banner -loglevel error -nostats -y -i {full_file_path} -ss {start_time} -to {end_time} -c:v libx264 -crf 23 -maxrate 2M -bufsize 2M -c:a copy -movflags +faststart {tmp_video_file_clip_path}"
|
||||
else:
|
||||
new_video_file_clip_filename = f'{filename_no_ext}_[clip].{to_type}'
|
||||
log.debug(new_video_file_clip_filename)
|
||||
|
||||
cmd = f"ffmpeg -hide_banner -loglevel error -nostats -y -i {full_file_path} -ss {start_time} -to {end_time} -c:v copy -c:a copy -movflags +faststart {tmp_video_file_clip_path}"
|
||||
|
||||
log.debug(cmd)
|
||||
|
||||
# Run the ffmpeg command
|
||||
args = shlex.split(cmd)
|
||||
try:
|
||||
result = subprocess.run(args, check=True, capture_output=True, text=True)
|
||||
log.debug(result.stdout)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log.exception('Error running ffmpeg command')
|
||||
return {'success': False, 'status_message': f'Error running ffmpeg command: {e}'}
|
||||
|
||||
|
||||
# *** Part 2: *** Save the converted hashed file to hosted_files directory.
|
||||
|
||||
file_info = await save_file_to_hosted_file(
|
||||
file_path = tmp_video_file_clip_path,
|
||||
filename = new_video_file_clip_filename,
|
||||
extension = to_type,
|
||||
account_id = account_id,
|
||||
link_to_type = link_to_type,
|
||||
link_to_id = link_to_id,
|
||||
)
|
||||
|
||||
# *** Part 3: *** Save information to database in hosted_file table (hosted_file_link table will be updated by an event_file table trigger)
|
||||
|
||||
new_hosted_file_id = None
|
||||
if file_info.get('saved'):
|
||||
# NOTE: Just in case look up in DB based on hash
|
||||
log.info('Look up in DB based on hash...')
|
||||
if hosted_file_sel_result := sql_select(
|
||||
table_name = 'hosted_file',
|
||||
field_name = 'hash_sha256',
|
||||
field_value = file_info['hash_sha256'],
|
||||
):
|
||||
log.warning('Found an existing host_file object_entry in the DB but the file was not found on the server!')
|
||||
# Got existing host_file object_entry!
|
||||
# Odd... the hash was found in the database, but the file had to be copied again.
|
||||
# If this happens then the file on the host server was probably deleted at some point.
|
||||
new_hosted_file_id = hosted_file_sel_result.get('id', None)
|
||||
# hosted_file_id_random = hosted_file_sel_result.get('id_random', None)
|
||||
hosted_file_dict = load_hosted_file_obj(hosted_file_id=new_hosted_file_id, model_as_dict=True)
|
||||
else:
|
||||
# This is normal since the file was not found on the host server and not found in the DB.
|
||||
# Create a new host_file object entry and new host_file.id_random.
|
||||
file_info['account_id'] = account_id
|
||||
# file_info['account_id_random'] = account_id_random
|
||||
hosted_file_obj = Hosted_File_Base(**file_info)
|
||||
if hosted_file_obj_result := create_hosted_file_obj(hosted_file_obj_new=hosted_file_obj):
|
||||
new_hosted_file_id = hosted_file_obj_result
|
||||
hosted_file_dict = load_hosted_file_obj(hosted_file_id=new_hosted_file_id, model_as_dict=True)
|
||||
else:
|
||||
log.warning('For some reason a host_file object entry could not be created.')
|
||||
new_hosted_file_id = None
|
||||
hosted_file_dict = hosted_file_obj.dict(by_alias=True, exclude_unset=True, exclude={'id', 'id_random'}) # pylint: disable=no-member
|
||||
log.debug(hosted_file_obj_result)
|
||||
|
||||
# NOTE: Currently sql_insert does not handle all successful inserts correctly. If there is not an autonum ID then it will return 0 as the ID.
|
||||
if link_to_type in ['event', 'event_location', 'event_session', 'event_presentation', 'event_presenter', 'event_badge', 'event_exhibit', 'event_person']:
|
||||
log.info('File is for event module. Trigger will create the hosted_file_link record.')
|
||||
elif create_hosted_file_link(
|
||||
account_id = account_id,
|
||||
hosted_file_id = new_hosted_file_id, # This for the new file created
|
||||
link_to_type = link_to_type,
|
||||
link_to_id = link_to_id,
|
||||
):
|
||||
log.info('The hosted file link was created.')
|
||||
pass # This if statement should be improved
|
||||
else:
|
||||
# This if statement should be improved
|
||||
log.debug('Because the hosted_file_link table does not have a primary autonum this check is incorrect even when successful.')
|
||||
log.debug('Something may have gone wrong while trying to create the hosted_file_link record.')
|
||||
log.debug('The hosted_file_link was probably created fine though.')
|
||||
log.debug(hosted_file_sel_result)
|
||||
else: return False
|
||||
|
||||
log.debug(hosted_file_dict)
|
||||
return mk_resp(data=hosted_file_dict, response=commons.response)
|
||||
# ### END ### API Hosted File ### clip_video() ###
|
||||
|
||||
|
||||
# Updated 2024-01-01
|
||||
@router.post('/clip_video_v1')
|
||||
async def clip_video_v1(
|
||||
video_file: UploadFile = File(...),
|
||||
start_time: str = Form(...),
|
||||
end_time: str = Form(...),
|
||||
|
||||
Reference in New Issue
Block a user