Compare commits
2 commits
4cce6c8cf8
...
d01c42c3a7
| Author | SHA1 | Date | |
|---|---|---|---|
| d01c42c3a7 | |||
| 288c2776c8 |
12 changed files with 158 additions and 48 deletions
3
.idea/.gitignore
vendored
Normal file
3
.idea/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
17
.idea/inspectionProfiles/Project_Default.xml
Normal file
17
.idea/inspectionProfiles/Project_Default.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="false" level="WARNING" enabled_by_default="false">
|
||||||
|
<option name="ignoredIdentifiers">
|
||||||
|
<list>
|
||||||
|
<option value="PySide6.QtWidgets.QHeaderView.*" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
|
<option name="processCode" value="true" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
7
.idea/misc.xml
Normal file
7
.idea/misc.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.13" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (python.fast-api-converter)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/python.fast-api-converter.iml" filepath="$PROJECT_DIR$/.idea/python.fast-api-converter.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
18
.idea/python.fast-api-converter.iml
Normal file
18
.idea/python.fast-api-converter.iml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.13 (python.fast-api-converter)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="PyDocumentationSettings">
|
||||||
|
<option name="format" value="PLAIN" />
|
||||||
|
<option name="myDocStringFormat" value="Plain" />
|
||||||
|
</component>
|
||||||
|
<component name="TestRunnerService">
|
||||||
|
<option name="PROJECT_TEST_RUNNER" value="py.test" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
BIN
app/__pycache__/main.cpython-313.pyc
Normal file
BIN
app/__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/__pycache__/video_class.cpython-313.pyc
Normal file
BIN
app/__pycache__/video_class.cpython-313.pyc
Normal file
Binary file not shown.
70
app/main.py
70
app/main.py
|
|
@ -26,6 +26,7 @@ convert_task = 0
|
||||||
|
|
||||||
# Settings
|
# Settings
|
||||||
language = ["ger", "eng"]
|
language = ["ger", "eng"]
|
||||||
|
subtitle_codec_blacklist = ["hdmv_pgs_subtitle", "dvd_subtitle"]
|
||||||
process_count = 1
|
process_count = 1
|
||||||
|
|
||||||
# Test
|
# Test
|
||||||
|
|
@ -46,28 +47,27 @@ async def queue_video():
|
||||||
convert_task += 1
|
convert_task += 1
|
||||||
asyncio.create_task(video_convert(obj))
|
asyncio.create_task(video_convert(obj))
|
||||||
|
|
||||||
async def video_convert(obj):
|
async def get_ffprobe(select, obj):
|
||||||
global convert_task
|
global convert_task
|
||||||
|
|
||||||
# Audio ------------------------------------------------------------------------------------------------------------
|
|
||||||
command = [
|
command = [
|
||||||
"ffprobe", "-v",
|
"ffprobe", "-v",
|
||||||
"error",
|
"error",
|
||||||
"-select_streams",
|
"-select_streams",
|
||||||
"a", "-show_entries",
|
f"{select}", "-show_entries",
|
||||||
"stream=index,channels,tags:stream_tags=language,tags:format=duration",
|
"stream=index,channels,codec_name,tags:stream_tags=language,tags:format=duration",
|
||||||
"-of", "json",
|
"-of", "json",
|
||||||
obj.source_file
|
obj.source_file
|
||||||
]
|
]
|
||||||
|
|
||||||
json_data = {}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(command, stdout=subprocess.PIPE, text=True)
|
result = subprocess.run(command, stdout=subprocess.PIPE, text=True)
|
||||||
json_data = json.loads(result.stdout)
|
json_data = json.loads(result.stdout)
|
||||||
print(json_data)
|
print(json_data)
|
||||||
|
|
||||||
obj.duration = json_data.get("format", {"duration":999}).get("duration")
|
duration = json_data.get("format", {"duration": 999}).get("duration")
|
||||||
|
obj.duration = float(duration) if duration.isdigit() else 0.0
|
||||||
|
return json_data
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
convert_task -= 1
|
convert_task -= 1
|
||||||
|
|
@ -75,6 +75,12 @@ async def video_convert(obj):
|
||||||
obj.error.append(f"ffprobe ---- {e}")
|
obj.error.append(f"ffprobe ---- {e}")
|
||||||
print(obj.error)
|
print(obj.error)
|
||||||
|
|
||||||
|
async def video_convert(obj):
|
||||||
|
global convert_task
|
||||||
|
|
||||||
|
json_data_audio = await get_ffprobe("a", obj)
|
||||||
|
json_data_subtitles = await get_ffprobe("s", obj)
|
||||||
|
|
||||||
# Konvertierung ----------------------------------------------------------------------------------------------------
|
# Konvertierung ----------------------------------------------------------------------------------------------------
|
||||||
command = [
|
command = [
|
||||||
"ffmpeg", "-y", "-i", obj.source_file,
|
"ffmpeg", "-y", "-i", obj.source_file,
|
||||||
|
|
@ -87,9 +93,9 @@ async def video_convert(obj):
|
||||||
"-svtav1-params", "tune=0:film-grain=8",
|
"-svtav1-params", "tune=0:film-grain=8",
|
||||||
]
|
]
|
||||||
|
|
||||||
if "streams" in json_data:
|
if "streams" in json_data_audio:
|
||||||
i = 0
|
i = 0
|
||||||
for audio_stream in json_data["streams"]:
|
for audio_stream in json_data_audio["streams"]:
|
||||||
if audio_stream.get("tags", {}).get("language", None) in language:
|
if audio_stream.get("tags", {}).get("language", None) in language:
|
||||||
command.extend([
|
command.extend([
|
||||||
"-map", f"0:{audio_stream['index']}",
|
"-map", f"0:{audio_stream['index']}",
|
||||||
|
|
@ -98,10 +104,26 @@ async def video_convert(obj):
|
||||||
f"-ac", str(audio_stream['channels'])
|
f"-ac", str(audio_stream['channels'])
|
||||||
])
|
])
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
command.extend(["-map", "0:s?", "-c:s", "copy"])
|
# Subtitle-Streams einbinden
|
||||||
|
if "streams" in json_data_subtitles:
|
||||||
|
for subtitle_stream in json_data_subtitles["streams"]:
|
||||||
|
if subtitle_stream.get("codec_name") not in subtitle_codec_blacklist:
|
||||||
|
if subtitle_stream.get("tags", {}).get("language", None) in language:
|
||||||
|
command.extend([
|
||||||
|
"-map", f"0:{subtitle_stream['index']}",
|
||||||
|
])
|
||||||
|
|
||||||
command.append(obj.output_file)
|
command.append(obj.output_file)
|
||||||
|
|
||||||
|
"""
|
||||||
|
ffmpeg_cm = ""
|
||||||
|
for cm in command:
|
||||||
|
ffmpeg_cm += f"{cm} "
|
||||||
|
|
||||||
|
print(ffmpeg_cm)
|
||||||
|
"""
|
||||||
|
|
||||||
# Prozess
|
# Prozess
|
||||||
try:
|
try:
|
||||||
process_video = await asyncio.create_subprocess_exec(
|
process_video = await asyncio.create_subprocess_exec(
|
||||||
|
|
@ -126,38 +148,20 @@ async def read_output():
|
||||||
while True:
|
while True:
|
||||||
active_processes = [obj for obj in video_files.values() if obj.progress]
|
active_processes = [obj for obj in video_files.values() if obj.progress]
|
||||||
|
|
||||||
|
print(active_processes)
|
||||||
|
|
||||||
if not active_processes:
|
if not active_processes:
|
||||||
await asyncio.sleep(10) # Kein aktives Video -> kurz warten
|
await asyncio.sleep(10) # Kein aktives Video -> kurz warten
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for obj in active_processes:
|
for obj in active_processes:
|
||||||
if obj.progress:
|
if obj.progress:
|
||||||
line = await obj.progress.stderr.read(2048)
|
line = await obj.progress.stderr.read(1024)
|
||||||
|
|
||||||
line_decoded = line.decode().strip()
|
line_decoded = line.decode().strip()
|
||||||
print(line_decoded)
|
print(line_decoded)
|
||||||
|
|
||||||
frame = re.findall(r"frame=\s*(\d+)", line_decoded)
|
obj.extract_convert_data(line_decoded)
|
||||||
obj.frame = frame[0] if frame else obj.frame
|
|
||||||
|
|
||||||
fps = re.findall(r"fps=\s*(\d+.\d+)", line_decoded)
|
|
||||||
obj.fps = fps[0] if fps else obj.fps
|
|
||||||
|
|
||||||
q = re.findall(r"q=\s*(\d+.\d+)", line_decoded)
|
|
||||||
obj.q = q[0] if q else obj.q
|
|
||||||
|
|
||||||
size = re.findall(r"size=\s*(\d+)", line_decoded)
|
|
||||||
obj.size = round(int(size[0]) / 1024, 2) if size else obj.size
|
|
||||||
|
|
||||||
time = re.findall(r"time=\s*(\d+:\d+:\d+)", line_decoded)
|
|
||||||
time_v = time[0] if time else time
|
|
||||||
obj.time = obj.time_in_sec(time_v)
|
|
||||||
|
|
||||||
bitrate = re.findall(r"bitrate=\s*(\d+)", line_decoded)
|
|
||||||
obj.bitrate = round(int(bitrate[0]) / 1024, 2) if bitrate else obj.bitrate
|
|
||||||
|
|
||||||
speed = re.findall(r"speed=\s*(\d+\.\d+)", line_decoded)
|
|
||||||
obj.speed = speed[0] if speed else obj.speed
|
|
||||||
|
|
||||||
json_data = json.dumps(obj.to_dict())
|
json_data = json.dumps(obj.to_dict())
|
||||||
await queue.put(json_data)
|
await queue.put(json_data)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class Video:
|
class Video:
|
||||||
|
|
@ -6,13 +8,13 @@ class Video:
|
||||||
self.id = id(self)
|
self.id = id(self)
|
||||||
self.source_file = path
|
self.source_file = path
|
||||||
self.output_file = f"{path.rsplit(".", 1)[0]}.webm"
|
self.output_file = f"{path.rsplit(".", 1)[0]}.webm"
|
||||||
self.frame = None
|
self.frame = 0
|
||||||
self.fps = None
|
self.fps = 0
|
||||||
self.q = None
|
self.q = 0
|
||||||
self.size = None
|
self.size = 0
|
||||||
self.time = 0
|
self.time = 0
|
||||||
self.bitrate = None
|
self.bitrate = 0
|
||||||
self.speed = None
|
self.speed = 0
|
||||||
self.finished = 0
|
self.finished = 0
|
||||||
self.progress = None
|
self.progress = None
|
||||||
self.duration = 1
|
self.duration = 1
|
||||||
|
|
@ -39,15 +41,50 @@ class Video:
|
||||||
"loading": self.loading
|
"loading": self.loading
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
def extract_convert_data(self, line_decoded):
|
||||||
def time_in_sec(time):
|
frame = re.findall(r"frame=\s*(\d+)", line_decoded)
|
||||||
if time != 0:
|
self.frame = frame[0] if frame else 0
|
||||||
h_m_s = str(time).split(":")
|
|
||||||
time_in_s = int(h_m_s[0]) * 3600 + int(h_m_s[1]) * 60 + int(h_m_s[2])
|
|
||||||
else:
|
|
||||||
time_in_s = 0
|
|
||||||
|
|
||||||
return time_in_s
|
fps = re.findall(r"fps=\s*(\d+.\d+)", line_decoded)
|
||||||
|
self.fps = fps[0] if fps else 0
|
||||||
|
|
||||||
|
q = re.findall(r"q=\s*(\d+.\d+)", line_decoded)
|
||||||
|
self.q = q[0] if q else 0
|
||||||
|
|
||||||
|
size = re.findall(r"size=\s*(\d+)", line_decoded)
|
||||||
|
self.size = self.convert_kb_mb(size[0]) if size else 0
|
||||||
|
|
||||||
|
time = re.findall(r"time=\s*(\d+:\d+:\d+)", line_decoded)
|
||||||
|
time_v = time[0] if time else ""
|
||||||
|
self.time = self.time_in_sec(time_v)
|
||||||
|
|
||||||
|
bitrate = re.findall(r"bitrate=\s*(\d+)", line_decoded)
|
||||||
|
self.bitrate = self.convert_kb_mb(bitrate[0]) if bitrate else 0
|
||||||
|
|
||||||
|
speed = re.findall(r"speed=\s*(\d+\.\d+)", line_decoded)
|
||||||
|
self.speed = speed[0] if speed else 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_time_format(s):
|
||||||
|
return bool(re.fullmatch(r"\d{2}:\d{2}:\d{2}(\.\d{1,2})?", s))
|
||||||
|
|
||||||
|
def time_in_sec(self, time_str):
|
||||||
|
if self.is_time_format(time_str):
|
||||||
|
try:
|
||||||
|
t = datetime.strptime(time_str, "%H:%M:%S.%f")
|
||||||
|
return t.hour * 3600 + t.minute * 3600 + t.second + t.microsecond / 1_000_000
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
def calc_loading(self):
|
def calc_loading(self):
|
||||||
self.loading = round(self.time / float(self.duration) * 100, None)
|
if self.duration != 0:
|
||||||
|
self.loading = round(self.time / self.duration * 100, None)
|
||||||
|
else:
|
||||||
|
self.loading = 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def convert_kb_mb(digits):
|
||||||
|
if digits.isdigit():
|
||||||
|
return round(int(digits) / 1024, 2)
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
@ -17,6 +17,10 @@ ws.onmessage = function(event) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ws.onerror = function(event) {
|
||||||
|
console.error("WebSocket Fehler:", event);
|
||||||
|
};
|
||||||
|
|
||||||
function createVideoElement(id, source, target, path) {
|
function createVideoElement(id, source, target, path) {
|
||||||
let container = document.createElement("div");
|
let container = document.createElement("div");
|
||||||
container.className = "video";
|
container.className = "video";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue