mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-07 20:15:24 +00:00
289 lines
9.5 KiB
Python
289 lines
9.5 KiB
Python
# 31.01.24
|
|
|
|
import sys
|
|
import time
|
|
import logging
|
|
import subprocess
|
|
|
|
from typing import List, Dict
|
|
|
|
|
|
# Internal utilities
|
|
from Src.Util._jsonConfig import config_manager
|
|
from Src.Util.os import check_file_existence, suppress_output
|
|
from Src.Util.console import console
|
|
from .util import need_to_force_to_ts, check_duration_v_a
|
|
from .capture import capture_ffmpeg_real_time
|
|
from ..M3U8 import M3U8_Codec
|
|
|
|
|
|
# Config
|
|
DEBUG_MODE = config_manager.get_bool("DEFAULT", "debug")
|
|
DEBUG_FFMPEG = "debug" if DEBUG_MODE else "error"
|
|
USE_CODEC = config_manager.get_bool("M3U8_CONVERSION", "use_codec")
|
|
USE_VCODEC = config_manager.get_bool("M3U8_CONVERSION", "use_vcodec")
|
|
USE_ACODEC = config_manager.get_bool("M3U8_CONVERSION", "use_acodec")
|
|
USE_BITRATE = config_manager.get_bool("M3U8_CONVERSION", "use_bitrate")
|
|
USE_GPU = config_manager.get_bool("M3U8_CONVERSION", "use_gpu")
|
|
FFMPEG_DEFAULT_PRESET = config_manager.get("M3U8_CONVERSION", "default_preset")
|
|
|
|
|
|
# Variable
|
|
TQDM_USE_LARGE_BAR = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_use_large_bar')
|
|
|
|
|
|
def join_video(video_path: str, out_path: str, codec: M3U8_Codec = None):
|
|
|
|
"""
|
|
Joins single ts video file to mp4
|
|
|
|
Args:
|
|
- video_path (str): The path to the video file.
|
|
- out_path (str): The path to save the output file.
|
|
- vcodec (str): The video codec to use. Defaults to 'copy'.
|
|
- acodec (str): The audio codec to use. Defaults to 'aac'.
|
|
- bitrate (str): The bitrate for the audio stream. Defaults to '192k'.
|
|
- force_ts (bool): Force video path to be mpegts as input.
|
|
"""
|
|
|
|
if not check_file_existence(video_path):
|
|
logging.error("Missing input video for ffmpeg conversion.")
|
|
sys.exit(0)
|
|
|
|
# Start command
|
|
ffmpeg_cmd = ['ffmpeg']
|
|
|
|
# Enabled the use of gpu
|
|
if USE_GPU:
|
|
ffmpeg_cmd.extend(['-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda'])
|
|
|
|
# Add mpegts to force to detect input file as ts file
|
|
if need_to_force_to_ts(video_path):
|
|
console.log("[red]Force input file to 'mpegts'.")
|
|
ffmpeg_cmd.extend(['-f', 'mpegts'])
|
|
vcodec = "libx264"
|
|
|
|
# Insert input video path
|
|
ffmpeg_cmd.extend(['-i', video_path])
|
|
|
|
# Add output args
|
|
if USE_CODEC:
|
|
if USE_VCODEC:
|
|
if codec.video_codec_name:
|
|
if not USE_GPU:
|
|
ffmpeg_cmd.extend(['-c:v', codec.video_codec_name])
|
|
else:
|
|
ffmpeg_cmd.extend(['-c:v', 'h264_nvenc'])
|
|
else:
|
|
console.log("[red]Cant find vcodec for 'join_audios'")
|
|
|
|
if USE_ACODEC:
|
|
if codec.audio_codec_name:
|
|
ffmpeg_cmd.extend(['-c:a', codec.audio_codec_name])
|
|
else:
|
|
console.log("[red]Cant find acodec for 'join_audios'")
|
|
|
|
if USE_BITRATE:
|
|
ffmpeg_cmd.extend(['-b:v', f'{codec.video_bitrate // 1000}k'])
|
|
ffmpeg_cmd.extend(['-b:a', f'{codec.audio_bitrate // 1000}k'])
|
|
|
|
else:
|
|
ffmpeg_cmd.extend(['-c', 'copy'])
|
|
|
|
# Ultrafast preset always or fast for gpu
|
|
if not USE_GPU:
|
|
ffmpeg_cmd.extend(['-preset', FFMPEG_DEFAULT_PRESET])
|
|
else:
|
|
ffmpeg_cmd.extend(['-preset', 'fast'])
|
|
|
|
# Overwrite
|
|
ffmpeg_cmd += [out_path, "-y"]
|
|
logging.info(f"FFmpeg command: {ffmpeg_cmd}")
|
|
|
|
# Run join
|
|
if DEBUG_MODE:
|
|
subprocess.run(ffmpeg_cmd, check=True)
|
|
else:
|
|
|
|
if TQDM_USE_LARGE_BAR:
|
|
capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join video")
|
|
print()
|
|
|
|
else:
|
|
console.log(f"[purple]FFmpeg [white][[cyan]Join video[white]] ...")
|
|
with suppress_output():
|
|
capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join video")
|
|
print()
|
|
|
|
time.sleep(0.5)
|
|
if not check_file_existence(out_path):
|
|
logging.error("Missing output video for ffmpeg conversion video.")
|
|
sys.exit(0)
|
|
|
|
|
|
def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: str, codec: M3U8_Codec = None):
|
|
"""
|
|
Joins audio tracks with a video file using FFmpeg.
|
|
|
|
Args:
|
|
- video_path (str): The path to the video file.
|
|
- audio_tracks (list[dict[str, str]]): A list of dictionaries containing information about audio tracks.
|
|
Each dictionary should contain the 'path' key with the path to the audio file.
|
|
- out_path (str): The path to save the output file.
|
|
"""
|
|
|
|
if not check_file_existence(video_path):
|
|
logging.error("Missing input video for ffmpeg conversion.")
|
|
sys.exit(0)
|
|
|
|
video_audio_same_duration = check_duration_v_a(video_path, audio_tracks[0].get('path'))
|
|
|
|
# Start command
|
|
ffmpeg_cmd = ['ffmpeg']
|
|
|
|
# Enabled the use of gpu
|
|
if USE_GPU:
|
|
ffmpeg_cmd.extend(['-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda'])
|
|
|
|
# Insert input video path
|
|
ffmpeg_cmd.extend(['-i', video_path])
|
|
|
|
# Add audio tracks as input
|
|
for i, audio_track in enumerate(audio_tracks):
|
|
if check_file_existence(audio_track.get('path')):
|
|
ffmpeg_cmd.extend(['-i', audio_track.get('path')])
|
|
else:
|
|
logging.error(f"Skip audio join: {audio_track.get('path')} dont exist")
|
|
|
|
# Map the video and audio streams
|
|
ffmpeg_cmd.append('-map')
|
|
ffmpeg_cmd.append('0:v') # Map video stream from the first input (video_path)
|
|
|
|
for i in range(1, len(audio_tracks) + 1):
|
|
ffmpeg_cmd.append('-map')
|
|
ffmpeg_cmd.append(f'{i}:a') # Map audio streams from subsequent inputs
|
|
|
|
# Add output args
|
|
if USE_CODEC:
|
|
if USE_VCODEC:
|
|
if codec.video_codec_name:
|
|
if not USE_GPU:
|
|
ffmpeg_cmd.extend(['-c:v', codec.video_codec_name])
|
|
else:
|
|
ffmpeg_cmd.extend(['-c:v', 'h264_nvenc'])
|
|
else:
|
|
console.log("[red]Cant find vcodec for 'join_audios'")
|
|
|
|
if USE_ACODEC:
|
|
if codec.audio_codec_name:
|
|
ffmpeg_cmd.extend(['-c:a', codec.audio_codec_name])
|
|
else:
|
|
console.log("[red]Cant find acodec for 'join_audios'")
|
|
|
|
if USE_BITRATE:
|
|
ffmpeg_cmd.extend(['-b:v', f'{codec.video_bitrate // 1000}k'])
|
|
ffmpeg_cmd.extend(['-b:a', f'{codec.audio_bitrate // 1000}k'])
|
|
|
|
else:
|
|
ffmpeg_cmd.extend(['-c', 'copy'])
|
|
|
|
# Ultrafast preset always or fast for gpu
|
|
if not USE_GPU:
|
|
ffmpeg_cmd.extend(['-preset', FFMPEG_DEFAULT_PRESET])
|
|
else:
|
|
ffmpeg_cmd.extend(['-preset', 'fast'])
|
|
|
|
# Use shortest input path for video and audios
|
|
if not video_audio_same_duration:
|
|
console.log("[red]Use shortest input.")
|
|
ffmpeg_cmd.extend(['-shortest', '-strict', 'experimental'])
|
|
|
|
# Overwrite
|
|
ffmpeg_cmd += [out_path, "-y"]
|
|
logging.info(f"FFmpeg command: {ffmpeg_cmd}")
|
|
|
|
# Run join
|
|
if DEBUG_MODE:
|
|
subprocess.run(ffmpeg_cmd, check=True)
|
|
else:
|
|
|
|
if TQDM_USE_LARGE_BAR:
|
|
capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join audio")
|
|
print()
|
|
|
|
else:
|
|
console.log(f"[purple]FFmpeg [white][[cyan]Join audio[white]] ...")
|
|
with suppress_output():
|
|
capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join audio")
|
|
print()
|
|
|
|
time.sleep(0.5)
|
|
if not check_file_existence(out_path):
|
|
logging.error("Missing output video for ffmpeg conversion audio.")
|
|
sys.exit(0)
|
|
|
|
|
|
def join_subtitle(video_path: str, subtitles_list: List[Dict[str, str]], out_path: str):
|
|
"""
|
|
Joins subtitles with a video file using FFmpeg.
|
|
|
|
Args:
|
|
- video (str): The path to the video file.
|
|
- subtitles_list (list[dict[str, str]]): A list of dictionaries containing information about subtitles.
|
|
Each dictionary should contain the 'path' key with the path to the subtitle file and the 'name' key with the name of the subtitle.
|
|
- out_path (str): The path to save the output file.
|
|
"""
|
|
|
|
if not check_file_existence(video_path):
|
|
logging.error("Missing input video for ffmpeg conversion.")
|
|
sys.exit(0)
|
|
|
|
# Start command
|
|
ffmpeg_cmd = ["ffmpeg", "-i", video_path]
|
|
|
|
# Add subtitle input files first
|
|
for subtitle in subtitles_list:
|
|
if check_file_existence(subtitle.get('path')):
|
|
ffmpeg_cmd += ["-i", subtitle['path']]
|
|
else:
|
|
logging.error(f"Skip subtitle join: {subtitle.get('path')} doesn't exist")
|
|
|
|
# Add maps for video and audio streams
|
|
ffmpeg_cmd += ["-map", "0:v", "-map", "0:a"]
|
|
|
|
# Add subtitle maps and metadata
|
|
for idx, subtitle in enumerate(subtitles_list):
|
|
ffmpeg_cmd += ["-map", f"{idx + 1}:s"]
|
|
ffmpeg_cmd += ["-metadata:s:s:{}".format(idx), "title={}".format(subtitle['name'])]
|
|
|
|
# Add output args
|
|
if USE_CODEC:
|
|
ffmpeg_cmd.extend(['-c:v', 'copy', '-c:a', 'copy', '-c:s', 'mov_text'])
|
|
else:
|
|
ffmpeg_cmd.extend(['-c', 'copy', '-c:s', 'mov_text'])
|
|
|
|
# Overwrite
|
|
ffmpeg_cmd += [out_path, "-y"]
|
|
logging.info(f"FFmpeg command: {ffmpeg_cmd}")
|
|
|
|
|
|
# Run join
|
|
if DEBUG_MODE:
|
|
subprocess.run(ffmpeg_cmd, check=True)
|
|
else:
|
|
|
|
if TQDM_USE_LARGE_BAR:
|
|
capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join subtitle")
|
|
print()
|
|
|
|
else:
|
|
console.log(f"[purple]FFmpeg [white][[cyan]Join subtitle[white]] ...")
|
|
with suppress_output():
|
|
capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join subtitle")
|
|
print()
|
|
|
|
time.sleep(0.5)
|
|
if not check_file_existence(out_path):
|
|
logging.error("Missing output video for ffmpeg conversion subtitle.")
|
|
sys.exit(0)
|