Spaces:
Sleeping
Sleeping
Alter logic to serve video on static endpoint
Browse files- Dockerfile +1 -1
- main.py +24 -16
- youtubeaudio.py +23 -19
Dockerfile
CHANGED
@@ -29,7 +29,7 @@ COPY --chown=user youtubeaudio.py $HOME/root
|
|
29 |
ARG model=base
|
30 |
ENV model=$model
|
31 |
RUN mkdir $HOME/root/models
|
32 |
-
RUN mkdir -p -m
|
33 |
COPY --from=build "/whisper/models/ggml-${model}.bin" "${HOME}/root/models/ggml-${model}.bin"
|
34 |
COPY --from=build /whisper/main /usr/local/bin/whisper
|
35 |
|
|
|
29 |
ARG model=base
|
30 |
ENV model=$model
|
31 |
RUN mkdir $HOME/root/models
|
32 |
+
RUN mkdir -p -m 775 /tmp/holosubs/tracks /tmp/holosubs/videos
|
33 |
COPY --from=build "/whisper/models/ggml-${model}.bin" "${HOME}/root/models/ggml-${model}.bin"
|
34 |
COPY --from=build /whisper/main /usr/local/bin/whisper
|
35 |
|
main.py
CHANGED
@@ -1,12 +1,13 @@
|
|
1 |
-
from fastapi import FastAPI, HTTPException
|
2 |
from pydantic import BaseModel
|
3 |
-
from youtubeaudio import
|
4 |
import subprocess
|
5 |
import os
|
6 |
import uuid
|
7 |
import logging
|
8 |
from fastapi.staticfiles import StaticFiles
|
9 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
10 |
|
11 |
class Url(BaseModel):
|
12 |
url: str
|
@@ -15,9 +16,11 @@ format = "%(asctime)s: %(message)s"
|
|
15 |
logging.basicConfig(format=format, level=logging.DEBUG,
|
16 |
datefmt="%H:%M:%S")
|
17 |
MODEL=os.environ['model']
|
|
|
|
|
18 |
|
19 |
app = FastAPI()
|
20 |
-
app.mount("/
|
21 |
|
22 |
# CORS
|
23 |
origins = ["*"]
|
@@ -30,27 +33,32 @@ app.add_middleware(
|
|
30 |
)
|
31 |
|
32 |
@app.post("/captions/")
|
33 |
-
def read_root(url: Url):
|
|
|
34 |
# Download wav file and get filename
|
35 |
-
ytaudio=
|
36 |
-
ytaudio.
|
37 |
-
|
38 |
# Resample file
|
39 |
ytaudio.resample('16k')
|
|
|
40 |
# Generate subtitles
|
41 |
-
|
42 |
-
|
43 |
-
logging.info(f'Writing to file {output_file}.vtt')
|
44 |
cmd=['/usr/local/bin/whisper','-m',f'/home/user/root/models/ggml-{MODEL}.bin'
|
45 |
-
,'-f',
|
46 |
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
47 |
(output, err) = p.communicate()
|
48 |
p_status = p.wait()
|
49 |
-
logging.info(output)
|
50 |
if err:
|
51 |
-
logging.error("
|
52 |
raise HTTPException(status_code=500, detail="Whisper translation failed")
|
53 |
-
|
54 |
-
|
55 |
-
|
|
|
|
|
|
|
|
|
|
|
56 |
|
|
|
1 |
+
from fastapi import FastAPI, HTTPException, Request
|
2 |
from pydantic import BaseModel
|
3 |
+
from youtubeaudio import YoutubeMedia
|
4 |
import subprocess
|
5 |
import os
|
6 |
import uuid
|
7 |
import logging
|
8 |
from fastapi.staticfiles import StaticFiles
|
9 |
from fastapi.middleware.cors import CORSMiddleware
|
10 |
+
import ffmpeg
|
11 |
|
12 |
class Url(BaseModel):
|
13 |
url: str
|
|
|
16 |
logging.basicConfig(format=format, level=logging.DEBUG,
|
17 |
datefmt="%H:%M:%S")
|
18 |
MODEL=os.environ['model']
|
19 |
+
BASE_DIR="/tmp/holosubs"
|
20 |
+
VIDEO_DIR=os.path.join(BASE_DIR,"videos")
|
21 |
|
22 |
app = FastAPI()
|
23 |
+
app.mount("/videos", StaticFiles(directory=VIDEO_DIR), name="videos")
|
24 |
|
25 |
# CORS
|
26 |
origins = ["*"]
|
|
|
33 |
)
|
34 |
|
35 |
@app.post("/captions/")
|
36 |
+
def read_root(url: Url, request: Request):
|
37 |
+
requestID=str(uuid.uuid4())
|
38 |
# Download wav file and get filename
|
39 |
+
ytaudio=YoutubeMedia(url, os.path.join(BASE_DIR,requestID))
|
40 |
+
ytaudio.download()
|
41 |
+
audio_filename, video_filename = ytaudio.audio_filename, ytaudio.video_filename
|
42 |
# Resample file
|
43 |
ytaudio.resample('16k')
|
44 |
+
|
45 |
# Generate subtitles
|
46 |
+
subtitle_filename=os.path.join("/tmp/holosubs/tracks", requestID)
|
47 |
+
logging.info(f'Output will be writen to {subtitle_filename}.srt')
|
|
|
48 |
cmd=['/usr/local/bin/whisper','-m',f'/home/user/root/models/ggml-{MODEL}.bin'
|
49 |
+
,'-f',audio_filename, '-of', subtitle_filename, '-tr', '-osrt', '-t', '8']
|
50 |
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
51 |
(output, err) = p.communicate()
|
52 |
p_status = p.wait()
|
|
|
53 |
if err:
|
54 |
+
logging.error("Translation failed with error",err)
|
55 |
raise HTTPException(status_code=500, detail="Whisper translation failed")
|
56 |
+
media=ffmpeg.input(video_filename)
|
57 |
+
ffmpeg.concat(media.video. \
|
58 |
+
filter('subtitles', subtitle_filename+".srt"), \
|
59 |
+
media.audio, v=1, a=1). \
|
60 |
+
output(os.path.join(VIDEO_DIR,requestID+".mp4")).run()
|
61 |
+
|
62 |
+
# Return video url delivered
|
63 |
+
return {"video": f'/videos/{requestID+".mp4"}'}
|
64 |
|
youtubeaudio.py
CHANGED
@@ -14,45 +14,49 @@ format = "%(asctime)s: %(message)s"
|
|
14 |
logging.basicConfig(format=format, level=logging.DEBUG,
|
15 |
datefmt="%H:%M:%S")
|
16 |
|
17 |
-
class
|
18 |
-
def __init__(self, url, dir="
|
19 |
self.url=url
|
20 |
self.dir=dir
|
|
|
|
|
21 |
|
22 |
-
def
|
23 |
ydl_opts = {
|
24 |
'outtmpl': os.path.join(self.dir, "%(id)s_%(epoch)s.%(ext)s"),
|
25 |
'logger': logging,
|
26 |
'progress_hooks': [self.progress_hook],
|
27 |
-
'format': '
|
|
|
28 |
'postprocessors': [{ # Extract audio using ffmpeg
|
29 |
'key': 'FFmpegExtractAudio',
|
30 |
-
'preferredcodec':
|
31 |
}]
|
32 |
}
|
33 |
with YoutubeDL(ydl_opts) as ydl:
|
34 |
error_code = ydl.download([self.url.url])
|
35 |
|
36 |
-
def resample(self,sr='16k'):
|
37 |
-
tmp_filename=os.path.join(self.dir,str(uuid.uuid4()))+".
|
38 |
-
ffmpeg.input(self.
|
39 |
-
shutil.move(tmp_filename, self.
|
40 |
-
logging.info(f"Succesfuly resampled {self.
|
41 |
|
42 |
def clean(self):
|
43 |
-
if not self.
|
44 |
logging.error("Audio not downloaded")
|
45 |
return
|
46 |
-
location=os.path.join(self.dir, self.
|
47 |
-
if os.path.exists(self.
|
48 |
-
os.remove(self.
|
49 |
-
logging.info(f"File {self.
|
50 |
-
self.
|
51 |
else:
|
52 |
-
print(f"File {self.
|
53 |
|
54 |
def progress_hook(self, d):
|
55 |
if d['status'] == 'finished':
|
56 |
-
self.
|
57 |
-
|
|
|
58 |
|
|
|
14 |
logging.basicConfig(format=format, level=logging.DEBUG,
|
15 |
datefmt="%H:%M:%S")
|
16 |
|
17 |
+
class YoutubeMedia:
|
18 |
+
def __init__(self, url, dir, audio_format="wav", video_format="mp4"):
|
19 |
self.url=url
|
20 |
self.dir=dir
|
21 |
+
self.audio_format=audio_format
|
22 |
+
self.video_format=video_format
|
23 |
|
24 |
+
def download(self):
|
25 |
ydl_opts = {
|
26 |
'outtmpl': os.path.join(self.dir, "%(id)s_%(epoch)s.%(ext)s"),
|
27 |
'logger': logging,
|
28 |
'progress_hooks': [self.progress_hook],
|
29 |
+
'format': f'{self.video_format}/bestvideo',
|
30 |
+
'keepvideo': True,
|
31 |
'postprocessors': [{ # Extract audio using ffmpeg
|
32 |
'key': 'FFmpegExtractAudio',
|
33 |
+
'preferredcodec': self.audio_format
|
34 |
}]
|
35 |
}
|
36 |
with YoutubeDL(ydl_opts) as ydl:
|
37 |
error_code = ydl.download([self.url.url])
|
38 |
|
39 |
+
def resample(self,sr='16k'): #defaults to 16k sampling rate
|
40 |
+
tmp_filename=os.path.join(self.dir,str(uuid.uuid4()))+"."+self.audio_format
|
41 |
+
ffmpeg.input(self.audio_filename).output(tmp_filename,ar=sr).run()
|
42 |
+
shutil.move(tmp_filename, self.audio_filename)
|
43 |
+
logging.info(f"Succesfuly resampled {self.audio_filename}")
|
44 |
|
45 |
def clean(self):
|
46 |
+
if not self.audio_filename:
|
47 |
logging.error("Audio not downloaded")
|
48 |
return
|
49 |
+
location=os.path.join(self.dir, self.audio_filename)
|
50 |
+
if os.path.exists(self.audio_filename):
|
51 |
+
os.remove(self.audio_filename)
|
52 |
+
logging.info(f"File {self.audio_filename} successfully removed")
|
53 |
+
self.audio_filename=None
|
54 |
else:
|
55 |
+
print(f"File {self.audio_filename} does not exist")
|
56 |
|
57 |
def progress_hook(self, d):
|
58 |
if d['status'] == 'finished':
|
59 |
+
self.audio_filename=os.path.join(self.dir, Path(d.get('info_dict').get('_filename')).stem + "."+self.audio_format)
|
60 |
+
self.video_filename=os.path.join(self.dir, Path(d.get('info_dict').get('_filename')).stem + "."+self.video_format)
|
61 |
+
print(f'Done downloading {self.audio_filename}, now post-processing ...')
|
62 |
|