From: Vásáry Dániel Date: Sat, 27 Jan 2024 23:10:45 +0000 (+0100) Subject: MediaSamurai initial import X-Git-Url: http://git.useribm.hu/?a=commitdiff_plain;h=46c703976c34ee5b1940ce76986527ad0915daec;p=mediacube.git MediaSamurai initial import --- diff --git a/media-samurai/KB.md b/media-samurai/KB.md new file mode 100644 index 00000000..af3f4c49 --- /dev/null +++ b/media-samurai/KB.md @@ -0,0 +1,7 @@ +https://trac.ffmpeg.org/wiki/AudioChannelManipulation + +ffmpeg -y -i multi_audiotest5.mkv -filter_complex "[0:a:0][0:a:1]amerge=inputs=2[a]" -map "[a]" -ac 2 -map 0:v mixed_audio_output.mkv + +megtartja az eredeti hangot is +ffmpeg -y -i multi_audiotest5.mkv -filter_complex "[0:a:0][0:a:1]amerge=inputs=2[a]" -map "[a]" -ac 2 -map 0 mixed_audio_output.mkv + diff --git a/media-samurai/api.py b/media-samurai/api.py new file mode 100644 index 00000000..87ecb69f --- /dev/null +++ b/media-samurai/api.py @@ -0,0 +1,63 @@ +from datetime import datetime +from typing import Optional + +from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles +from fastapi import BackgroundTasks, FastAPI +from pydantic import BaseModel +from transcode import TranscodeItem, TranscodeStatus, TranscodeJob, background_tasks_results, simulate_background_task + + +# https://docs.pydantic.dev/2.4/concepts/models/ +class TaskStatusResponse(BaseModel): + task_id: str + started: Optional[datetime] + finished: Optional[datetime] + status: Optional[TranscodeStatus] + progress: int + + +app = FastAPI() + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) +app.mount("/video", StaticFiles(directory="hls"), name="VIDEO") + + +@app.post("/submit") +async def submit(item: TranscodeItem, background_tasks: BackgroundTasks): + task_id = str(len(background_tasks_results) + 1) + background_tasks.add_task(simulate_background_task, task_id, item.delay) + job = TranscodeJob() + job.status = TranscodeStatus.RUNNING + background_tasks_results[task_id] = job + return {"task_id": task_id} + + +@app.get("/status/{task_id}", response_model=TaskStatusResponse) +async def status(task_id: str): + + if background_tasks_results.__contains__(task_id): + job = background_tasks_results.get(task_id, TranscodeJob()) + return {"task_id": task_id, + "status": job.status, + "progress": job.progress, + "started": job.started, + "finished": job.finished} + else: + return {"task_id": "0", + "status": None, + "progress": 0, + "started": None, + "finished": None} + + +@app.get("/test") +async def get_test(): + return {"test": "TEST"} + diff --git a/media-samurai/hls.py b/media-samurai/hls.py new file mode 100644 index 00000000..dde8a279 --- /dev/null +++ b/media-samurai/hls.py @@ -0,0 +1,63 @@ +import shutil + +from ffmpeg import FFmpeg, Progress, FFmpegError +from pymediainfo import MediaInfo + + +def main(): + input_file = r'd:\data\video\hls\ma.mkv' + + media_info = MediaInfo.parse(input_file) + for track in media_info.tracks: + if track.track_type == "Video": + frame_count = int(track.frame_count) + # print("Duration (raw value):", track.duration) + + output_directory = 'hls/out/' + output_file = output_directory + r'%v/prog_index.m3u8' + shutil.rmtree(output_directory, ignore_errors=True) + + output_options = { + 'preset': 'slow', + 'g': 48, + 'sc_threshold': 0, + 'map': ['0:0', '0:1', '0:8'], + 's:v:0': '960x540', + 'c:v:0': 'libx264', + 'b:v:0': '2000k', + 'c:a:0': 'aac', + 'c:a:1': 'aac', + 'var_stream_map': 'a:0,agroup:audio,default:yes a:1,agroup:audio v:0,agroup:audio', + 'master_pl_name': 'master.m3u8', + 'f': 'hls', + 'hls_time': 6, + 'hls_list_size': 0, + 'hls_segment_filename': f'{output_directory}/%v/fileSequence%03d.ts', + } + + ffmpeg = ( + FFmpeg() + .option("y") + .input(input_file) + .output( + output_file, + output_options + ) + ) + + @ffmpeg.on("start") + def on_start(arguments: list[str]): + print("arguments:", arguments) + + @ffmpeg.on("progress") + def on_progress(progress: Progress): + print(round(progress.frame * 100 / frame_count)) + + try: + ffmpeg.execute() + except FFmpegError as e: + print(e.args) + + +if __name__ == "__main__": + main() diff --git a/media-samurai/hls/001.bat b/media-samurai/hls/001.bat new file mode 100644 index 00000000..b832b8b0 --- /dev/null +++ b/media-samurai/hls/001.bat @@ -0,0 +1,11 @@ +ffmpeg -y -i d:\data\video\hls\oceans.mp4 ^ + -preset slow -g 48 -sc_threshold 0 ^ + -map 0:0 -map 0:1 -map 0:0 -map 0:1 ^ + -s:v:0 640x360 -c:v:0 libx264 -b:v:0 365k ^ + -s:v:1 960x540 -c:v:1 libx264 -b:v:1 2000k ^ + -c:a copy ^ + -var_stream_map "v:0,a:0 v:1,a:1" ^ + -master_pl_name master.m3u8 ^ + -f hls -hls_time 6 -hls_list_size 0 ^ + -hls_segment_filename "out/v%%v/fileSequence%%d.ts" ^ + out/v%%v/prog_index.m3u8 \ No newline at end of file diff --git a/media-samurai/hls/002.bat b/media-samurai/hls/002.bat new file mode 100644 index 00000000..97cb32a1 --- /dev/null +++ b/media-samurai/hls/002.bat @@ -0,0 +1,11 @@ +ffmpeg -y -i d:\data\video\hls\ma.mkv ^ + -preset slow -g 48 -sc_threshold 0 ^ + -map 0:0 -map 0:1 -map 0:8 ^ + -s:v:0 960x540 -c:v:0 libx264 -b:v:0 2000k ^ + -c:a:0 aac ^ + -c:a:1 aac ^ + -var_stream_map "a:0,agroup:audio,default:yes a:1,agroup:audio v:0,agroup:audio" ^ + -master_pl_name master.m3u8 ^ + -f hls -hls_time 6 -hls_list_size 0 ^ + -hls_segment_filename "out/%%v/fileSequence%%03d.ts" ^ + out/%%v/prog_index.m3u8 diff --git a/media-samurai/hls/003.bat b/media-samurai/hls/003.bat new file mode 100644 index 00000000..5d5da674 --- /dev/null +++ b/media-samurai/hls/003.bat @@ -0,0 +1,15 @@ +ffmpeg -y -i d:\data\video\hls\ma.mkv ^ +-preset slow -g 48 -sc_threshold 0 ^ +-map 0:0 -map 0:1 -map 0:8 ^ +-s:v:0 960x540 -c:v:0 libx264 -b:v:0 2000k ^ +-c:a:0 aac ^ +-c:a:1 aac ^ +-var_stream_map "a:0,agroup:audio,default:yes a:1,agroup:audio v:0,agroup:audio" ^ +-f hls ^ +-hls_time 6 ^ +-hls_playlist_type vod ^ +-hls_segment_type mpegts ^ +-hls_flags independent_segments ^ +-hls_segment_filename "out/%%v/fileSequence%%03d.ts" ^ +-master_pl_name master.m3u8 ^ +out/%%v/prog_index.m3u8 diff --git a/media-samurai/hls/KB.md b/media-samurai/hls/KB.md new file mode 100644 index 00000000..397a7e63 --- /dev/null +++ b/media-samurai/hls/KB.md @@ -0,0 +1,10 @@ +https://hlsbook.net/creating-a-master-playlist-with-ffmpeg/ +http://underpop.online.fr/f/ffmpeg/help/hls-2.htm.gz +https://trac.ffmpeg.org/wiki/AudioChannelManipulation +https://trac.ffmpeg.org/wiki/Map + +tools https://github.com/videojs/http-streaming#tools +online coding https://replit.com/@vasary/Phind-Code-Snippet#pyproject.toml +video host https://api.video + +https://mediaarea.net/hr/MediaInfo/Support/Fields \ No newline at end of file diff --git a/media-samurai/hls/index.html b/media-samurai/hls/index.html new file mode 100644 index 00000000..6f5493fc --- /dev/null +++ b/media-samurai/hls/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/media-samurai/hls/video-bipbop.html b/media-samurai/hls/video-bipbop.html new file mode 100644 index 00000000..22127321 --- /dev/null +++ b/media-samurai/hls/video-bipbop.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/media-samurai/info.py b/media-samurai/info.py new file mode 100644 index 00000000..ef0de94f --- /dev/null +++ b/media-samurai/info.py @@ -0,0 +1,29 @@ +from pprint import pprint +from pymediainfo import MediaInfo + + +def audio_tracks_count(filename): + media_info = MediaInfo.parse(filename) + return len(media_info.audio_tracks) + + +def showinfo(filename): + media_info = MediaInfo.parse(filename) + # Tracks can be accessed via the 'tracks' attribute or through shortcuts + # such as 'image_tracks', 'audio_tracks', 'video_tracks', etc. + video_track = len(media_info.video_tracks) + audio_track = len(media_info.audio_tracks) + print( + f"Video tracks {video_track}, audio tracks {audio_track}" + ) + + for track in media_info.tracks: + if track.track_type == "Video": + print("Bit rate: {t.bit_rate}, Frame rate: {t.frame_rate}, " + "Format: {t.format}".format(t=track)) + print("Duration (raw value):", track.duration) + print("Duration (other values:") + pprint(track.other_duration) + elif track.track_type == "Audio": + print("Track data:") + pprint(track.to_data()) diff --git a/media-samurai/main.py b/media-samurai/main.py new file mode 100644 index 00000000..472b7800 --- /dev/null +++ b/media-samurai/main.py @@ -0,0 +1,27 @@ +import uvicorn +from api import app +from site_icon import site_icon_lizzard +from nicegui import ui + + +def start_job(): + ui.notify("Start job!") + + +ui.label("MEDIA SAMURAI") +grid = ui.aggrid({ + "columnDefs": [ + {"headerName": "Name", "field": "name"}, + {"headerName": "Type", "field": "type"}, + ], + "rowData": [], +}) +grid.props('inline height=500px') + +with ui.row(): + ui.button('Start', on_click=start_job).props('small outline') + + +ui.run_with(app, favicon=site_icon_lizzard) +if __name__ == "__main__": + uvicorn.run(app, port=8181) diff --git a/media-samurai/requirements.txt b/media-samurai/requirements.txt new file mode 100644 index 00000000..4439ec33 --- /dev/null +++ b/media-samurai/requirements.txt @@ -0,0 +1,7 @@ +uvicorn~=0.22.0 +fastapi~=0.104.1 +pydantic~=2.4.2 +nicegui~=1.4.2 +pymediainfo~=6.1.0 +tqdm~=4.66.1 +gevent~=23.9.1 \ No newline at end of file diff --git a/media-samurai/site_icon.py b/media-samurai/site_icon.py new file mode 100644 index 00000000..3e4a8b7e --- /dev/null +++ b/media-samurai/site_icon.py @@ -0,0 +1,27 @@ +site_icon_lizzard = ''' + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + + + + + + + + + + + + + + + + +''' diff --git a/media-samurai/transcode.py b/media-samurai/transcode.py new file mode 100644 index 00000000..2a59b191 --- /dev/null +++ b/media-samurai/transcode.py @@ -0,0 +1,39 @@ +from concurrent.futures import ThreadPoolExecutor +from datetime import datetime + +from pydantic import BaseModel +from enum import Enum +import time + +background_tasks_results = {} +executor = ThreadPoolExecutor(max_workers=5) + + +class TranscodeItem(BaseModel): + name: str + delay: int + + +class TranscodeStatus(str, Enum): + PENDING = "PENDING" + RUNNING = "RUNNING" + NONE = "NONE" + COMPLETED = "COMPLETED" + + +class TranscodeJob: + def __init__(self): + self.status = TranscodeStatus.NONE + self.progress = 0 + self.started = None + self.finished = None + + +def simulate_background_task(task_id, duration): + job = background_tasks_results[task_id] + job.started = datetime.now() + for i in range(0, 11): + job.progress = i * 10 + time.sleep(duration) + job.finished = datetime.now() + job.status = TranscodeStatus.COMPLETED