Improved the video file clip function

This commit is contained in:
Scott Idem
2025-01-07 15:45:19 -05:00
parent c78afbbc5c
commit 78ce11a30d

View File

@@ -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(...),