first commit
This commit is contained in:
commit
0bf37dc9eb
139
main.py
Normal file
139
main.py
Normal file
@ -0,0 +1,139 @@
|
||||
import threading
|
||||
import subprocess
|
||||
|
||||
def yt(url, then_callback):
|
||||
filename = subprocess.getoutput(f'yt-dlp --print filename "{url}"')
|
||||
def run_in_thread():
|
||||
proc = subprocess.run(["yt-dlp","-o",'data/%(title)s.%(ext)s',url])
|
||||
then_callback(filename)
|
||||
thread = threading.Thread(target=run_in_thread)
|
||||
thread.start()
|
||||
return filename # thread
|
||||
|
||||
from flask import Flask, render_template, send_from_directory
|
||||
from flask_socketio import SocketIO
|
||||
|
||||
app = Flask(__name__)
|
||||
socketio = SocketIO(app)
|
||||
|
||||
player = None
|
||||
|
||||
@app.route('/')
|
||||
def ws_index(): return render_template('index.html')
|
||||
|
||||
from urllib.parse import unquote
|
||||
@app.route('/data/<path:filename>')
|
||||
def ws_data(filename):
|
||||
print(unquote(filename))
|
||||
return send_from_directory('data',unquote(filename))
|
||||
# range_header = request.headers.get('Range', None)
|
||||
# byte1, byte2 = 0, None
|
||||
# if range_header:
|
||||
# match = re.search(r'(\d+)-(\d*)', range_header)
|
||||
# groups = match.groups()
|
||||
# if groups[0]: byte1 = int(groups[0])
|
||||
# if groups[1]: byte2 = int(groups[1])
|
||||
# chunk, start, length, file_size = get_chunk(byte1, byte2)
|
||||
# resp = Response(chunk, 206, mimetype='video/mp4', content_type='video/mp4', direct_passthrough=True)
|
||||
# resp.headers.add('Content-Range', 'bytes {0}-{1}/{2}'.format(start, start + length - 1, file_size))
|
||||
# return resp
|
||||
|
||||
@socketio.on('request_state')
|
||||
def ws_request_state(): player.emit_full()
|
||||
|
||||
@socketio.on('request_url')
|
||||
def ws_request_url(url): player.open_url(url)
|
||||
|
||||
@socketio.on('request_time')
|
||||
def ws_request_time(time): player.set_time(time)
|
||||
|
||||
@socketio.on('request_play')
|
||||
def ws_request_play(): player.play()
|
||||
|
||||
@socketio.on('request_pause')
|
||||
def ws_request_pause(): player.pause()
|
||||
|
||||
### video-player stuff
|
||||
import sys
|
||||
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QSizePolicy
|
||||
from PyQt5.QtCore import Qt, QUrl
|
||||
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
|
||||
from PyQt5.QtMultimediaWidgets import QVideoWidget
|
||||
|
||||
class VideoWindow(QMainWindow):
|
||||
def __init__(self, parent=None):
|
||||
super(VideoWindow, self).__init__(parent)
|
||||
self.setWindowTitle("piplayer")
|
||||
|
||||
videoWidget = QVideoWidget()
|
||||
videoWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
self.setCentralWidget(videoWidget)
|
||||
|
||||
self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
|
||||
self.mediaPlayer.setVideoOutput(videoWidget)
|
||||
self.mediaPlayer.stateChanged.connect(self.emit_state)
|
||||
self.mediaPlayer.positionChanged.connect(self.emit_time)
|
||||
self.mediaPlayer.durationChanged.connect(self.emit_duration)
|
||||
self.mediaPlayer.error.connect(self.handleError)
|
||||
|
||||
# this looks utterly stupid.
|
||||
self.showFullScreen()
|
||||
size = app.primaryScreen().size()
|
||||
self.resize(size.width(), size.height())
|
||||
|
||||
self.current_file = ""
|
||||
self.history = [] # TODO
|
||||
|
||||
def open_file(self, path):
|
||||
self.history.append(path)
|
||||
self.current_file=path
|
||||
import pathlib
|
||||
current_path = pathlib.Path(__file__).parent.resolve()
|
||||
abs_path = f"{current_path}/data/{path}"
|
||||
self.mediaPlayer.setMedia(QMediaContent(QUrl.fromLocalFile(abs_path)))
|
||||
self.play()
|
||||
|
||||
def emit_duration(self,duration): socketio.emit('duration' ,duration/1000,broadcast=True)
|
||||
def emit_time(self,time): socketio.emit('time' ,time /1000,broadcast=True)
|
||||
def emit_state(self,state): socketio.emit('state' ,state == QMediaPlayer.PlayingState,broadcast=True)
|
||||
def emit_current_file(self): socketio.emit('current_file',self.current_file,broadcast=True)
|
||||
|
||||
def emit_full(self):
|
||||
socketio.emit('current_file',self.current_file)
|
||||
socketio.emit('duration' ,self.mediaPlayer.duration()/1000)
|
||||
socketio.emit('time' ,self.mediaPlayer.position()/1000)
|
||||
socketio.emit('state' ,self.mediaPlayer.state() == QMediaPlayer.PlayingState)
|
||||
|
||||
def open_url(self, url):
|
||||
pass
|
||||
print(f"requested {url}")
|
||||
def post_dl(filename):
|
||||
self.open_file(filename)
|
||||
self.emit_full()
|
||||
filename = yt(url, post_dl)
|
||||
self.history.append(url)
|
||||
self.open_file(filename)
|
||||
self.emit_full()
|
||||
# TODO self.open_file(downloaded_file)
|
||||
|
||||
def play(self):
|
||||
self.mediaPlayer.play()
|
||||
self.emit_state(self.mediaPlayer.state())
|
||||
|
||||
def pause(self):
|
||||
self.mediaPlayer.pause()
|
||||
self.emit_state(self.mediaPlayer.state())
|
||||
|
||||
def set_time(self, time):
|
||||
self.mediaPlayer.setPosition(int(1000*time))
|
||||
self.emit_time(self.mediaPlayer.position())
|
||||
|
||||
def handleError(self):
|
||||
print("Error: " + self.mediaPlayer.errorString())
|
||||
|
||||
threading.Thread(target=lambda: socketio.run(app,host="0.0.0.0",port=5000)).start()
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
player = VideoWindow()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
4
readme
Normal file
4
readme
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
install dependencies :
|
||||
`apt install python3-flask-socketio python3-flask`
|
||||
|
||||
159
templates/index.html
Normal file
159
templates/index.html
Normal file
@ -0,0 +1,159 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<style>
|
||||
html, body { display:grid; margin:0; padding:0; }
|
||||
piplay-gridv {
|
||||
display: grid;
|
||||
}
|
||||
piplay-gridh {
|
||||
display: grid;
|
||||
width:100%;
|
||||
grid-auto-flow: column;
|
||||
}
|
||||
#playlist {
|
||||
overflow-y: auto;
|
||||
}
|
||||
#playlist > piplay-gridh {
|
||||
height:2em;
|
||||
grid-template-columns: 1fr 3em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<piplay-gridv style="grid-template-rows: max-content 2em 1fr;max-height:100vh">
|
||||
<video controls id="video_ui" muted style="width:100vw;max-height:80vh"></video>
|
||||
|
||||
<piplay-gridh>
|
||||
<input id="URL-input" style="width:100%" placeholder="URL" value="">
|
||||
<button id="URL-add">play</button>
|
||||
</piplay-gridh>
|
||||
|
||||
<piplay-gridv id="playlist">
|
||||
<!--
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
<piplay-gridh><div>coucou</div><button>x</button></piplay-gridh>
|
||||
-->
|
||||
</piplay-gridv>
|
||||
<!--
|
||||
<input type="range" min=0 max=1 step=0.0000001 id="current_time">
|
||||
<piplay-gridh style="height:32px">
|
||||
<button>prev</button>
|
||||
<button id="btn_+10s">-10s</button>
|
||||
<button id="btn_play">play</button>
|
||||
<button id="btn_-10s">+10s</button>
|
||||
<button>next</button>
|
||||
</piplay-gridh>
|
||||
-->
|
||||
<div id="debug"></div>
|
||||
</piplay-gridv>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js" integrity="sha512-q/dWJ3kcmjBLU4Qc47E4A9kTB4m3wuTY7vkFJDTZKjTs8jhyGQnaUrxa0Ytd0ssMZhbNua9hE+E7Qv1j+DyZwA==" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
function debug(msg) {
|
||||
let p = document.createElement("p")
|
||||
p.innerHTML = msg
|
||||
document.getElementById('debug').prepend(p)
|
||||
}
|
||||
|
||||
let time_slider = document.getElementById("current_time")
|
||||
let btn_play = document.getElementById("btn_play")
|
||||
let video_ui = document.getElementById("video_ui")
|
||||
|
||||
const socket = io()
|
||||
socket.on('connect' ,() => socket.emit('request_state'))
|
||||
|
||||
// ###### UI - insert new video
|
||||
// let URL_input = document.getElementById("URL-input")
|
||||
document.getElementById("URL-add").addEventListener("click", e => {
|
||||
let url = document.getElementById("URL-input").value
|
||||
socket.emit('request_url',url)
|
||||
})
|
||||
// ######
|
||||
|
||||
// // light version
|
||||
// socket.on('duration', d => time_slider.max = d)
|
||||
// socket.on('time' , t => time_slider.value = t)
|
||||
// socket.on('state' , s => btn_play.innerHTML = s ? "pause":"play")
|
||||
|
||||
// time_slider.addEventListener('input', e=> socket.emit('request_time',parseFloat(time_slider.value)))
|
||||
|
||||
// btn_play.addEventListener("click", e=> {
|
||||
// if(state.playing) socket.emit('request_pause')
|
||||
// else socket.emit('request_play')
|
||||
// })
|
||||
|
||||
// video version
|
||||
let last_path = ""
|
||||
let state = {}
|
||||
|
||||
let prevent_seek_event = false
|
||||
let prevent_seek_event_to = 0
|
||||
|
||||
socket.on('current_file',path => {video_ui.src = `data/${path}`;video_ui.play();})
|
||||
socket.on('state' ,s => {s ? video_ui.play() : video_ui.pause()})
|
||||
socket.on('time' ,t => {
|
||||
const t_jump_tolerance = 3.0
|
||||
if (Math.abs(video_ui.currentTime-t)>t_jump_tolerance) {
|
||||
// discontinuous jump
|
||||
video_ui.currentTime = t
|
||||
// TODO : dirty fix to prevent "seeking" everywhere...
|
||||
prevent_seek_event = true
|
||||
clearTimeout(prevent_seek_event_to)
|
||||
prevent_seek_event_to = setTimeout(()=>prevent_seek_event = false,100)
|
||||
} else {
|
||||
// change playback rate to cathchup
|
||||
let delay = 0.5*(t-video_ui.currentTime)
|
||||
video_ui.playbackRate = Math.exp(delay)
|
||||
}
|
||||
})
|
||||
|
||||
video_ui.addEventListener("seeking", e=> {
|
||||
if(!prevent_seek_event) {
|
||||
socket.emit('request_time',video_ui.currentTime)
|
||||
} else console.log("seek prevented")
|
||||
})
|
||||
// video_ui.addEventListener("play", e=> {
|
||||
// if(!prevent_seek_event) socket.emit('request_play')
|
||||
// })
|
||||
// video_ui.addEventListener("pause", e=> {
|
||||
// if(!prevent_seek_event) socket.emit('request_pause')
|
||||
// })
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user