123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- #!/usr/bin/python3
- # Copyright 2022, 2024 Pavel Machek, GPLv2+
- import os, sys, time, copy, subprocess
- import gi
- gi.require_version('Gst', '1.0')
- from gi.repository import Gst, GLib
- import os
- import time
- # https://stackoverflow.com/questions/11779490/how-to-add-a-new-audio-not-mixing-into-a-video-using-ffmpeg
- # https://ottverse.com/create-video-from-images-using-ffmpeg/
- # https://github.com/kkroening/ffmpeg-python/issues/95
- # sudo apt install ffmpeg
- # Usage: mpegize convert
- # head -c 1000 < /dev/zero > /tmp/delme.sm/1.foo.sa
- def gst_convert(mega_dir, out_file, use_jpeg):
- def sa_read(name, t):
- with open(name, "rb") as file:
- rgb_data = file.read(10*1024*1024)
- caps_string = "audio/x-raw,format=U16LE,channels=2,rate=48000,layout=interleaved,channel-mask=3"
- caps = Gst.Caps.from_string(caps_string)
- buffer = Gst.Buffer.new_wrapped(rgb_data)
- if False:
- time.sleep(1/30.)
- # nanoseconds
- buffer.pts = time.time() * 1000*1000*1000
- buffer.dts = time.time() * 1000*1000*1000
- elif True:
- buffer.pts = t
- buffer.dts = t
- buffer.duration = (1000*1000*1000)/10.
- return buffer, caps
- def sa_src(appsrc):
- def on_need_data(appsrc, data, length):
- name = audio.get_path()
- if name == None or name[-22:] != ".48000-s16le-stereo.sa":
- appsrc.emit("end-of-stream")
- print("End of audio stream")
- return
- t = audio.get_time()
- #print("Audio: ", name, " need ", data, t)
- buffer, caps = sa_read(name, t)
- os.unlink(name)
- appsrc.set_property("caps", caps)
- appsrc.emit("push-buffer", buffer)
- appsrc.set_property("format", Gst.Format.TIME)
- appsrc.set_property("is-live", False)
- appsrc.set_property("block", True)
- name = audio.get_path()
- buffer, caps = sa_read(name, 0)
- appsrc.set_property("caps", caps)
- #appsrc.emit("push-buffer", buffer)
- s = appsrc.connect("need-data", on_need_data, "")
- print("Connect", s)
- class grwBase:
- def init(m, dir):
- m.dir = dir
- m.slen = len(m.suffix)
- m.start_time = 0
- m.scan()
- print("Movie", len(m.list))
- def scan(m):
- m.list = os.listdir(m.dir)
- m.list.sort()
- m.length = len(m.list)
- def get_path(m):
- s = m.get_name()
- if s: return m.dir + s
- return s
- def get_name(m):
- m.scan()
- #print("Get path -- ")
- while True:
- if (len(m.list)) == 0:
- return None
- #print("Get path: ", m.list[0], m.suffix)
- if m.list[0][-m.slen:] != m.suffix:
- m.pop()
- continue
- return m.list[0]
- def get_time(m):
- s = m.get_name()
- s = s[:-m.slen]
- t = int(s)
- res = t * 1000 - m.start_time
- t = t / (1000*1000.)
- while (time.time() - t < 1):
- print("Too fast: ", time.time(), t, file=sys.stderr)
- print("Message: WA")
- sys.stdout.flush()
- time.sleep(.1)
- return res
- def pop(m):
- m.list = m.list[1:]
- def progress(m):
- i = len(m.list)
- print("Message: %d" % i)
- sys.stdout.flush()
- class grwVideo(grwBase):
- suffix = ".grw"
- def __init__(m, dir):
- m.init(dir)
- class grwJPEG(grwBase):
- suffix = ".jpeg.sv"
- def __init__(m, dir):
- m.init(dir + "sm/")
- class grwAudio(grwVideo):
- suffix = ".48000-s16le-stereo.sa"
- def __init__(m, dir):
- m.init(dir + "sm/")
- def grw_read(name, t):
- with open(name, "rb") as file:
- rgb_data = file.read(10*1024*1024)
- i = len(rgb_data)
- i -= 1
- while rgb_data[i] != 0:
- i -= 1
- footer = rgb_data[i+1:]
- sp = str(footer, 'ascii').split('\n')
- # Create caps for the file
- caps_string = sp[0][6:]
- caps = Gst.Caps.from_string(caps_string)
- if sp[0][:6] != "Caps: ":
- print("Bad footer")
- if sp[1][:6] != "Size: ":
- print("Bad footer")
- if sp[-1] != "GRW":
- print("Missing GRW footer")
- buffer = Gst.Buffer.new_wrapped(rgb_data)
- # This does not work for interactive use.
- if False:
- time.sleep(1/30.)
- # nanoseconds
- buffer.pts = time.time() * 1000*1000*1000
- buffer.dts = time.time() * 1000*1000*1000
- elif True:
- buffer.pts = t
- buffer.dts = t
- buffer.duration = (1000*1000*1000)/30.
- return buffer, caps
- def grwsrc(appsrc):
- def on_need_data(appsrc, data, length):
- name = movie.get_path()
- if name == None or name[-4:] != ".grw":
- appsrc.emit("end-of-stream")
- print("End of video stream")
- return
- t = movie.get_time()
- #print("Video: ", name, t)
- movie.progress()
- buffer, caps = grw_read(name, t)
- os.unlink(name)
- appsrc.set_property("caps", caps)
- appsrc.emit("push-buffer", buffer)
- appsrc.set_property("format", Gst.Format.TIME)
- appsrc.set_property("is-live", False)
- appsrc.set_property("block", True)
- name = movie.get_path()
- buffer, caps = grw_read(name, 0)
- appsrc.set_property("caps", caps)
- #appsrc.emit("push-buffer", buffer)
- s = appsrc.connect("need-data", on_need_data, "")
- print("Connect", s)
- def jpeg_read(name, t):
- with open(name, "rb") as file:
- rgb_data = file.read(10*1024*1024)
- i = len(rgb_data)
- buffer = Gst.Buffer.new_wrapped(rgb_data)
- caps_string = "image/jpeg"
- caps = Gst.Caps.from_string(caps_string)
- # This does not work for interactive use.
- if False:
- time.sleep(1/30.)
- # nanoseconds
- buffer.pts = time.time() * 1000*1000*1000
- buffer.dts = time.time() * 1000*1000*1000
- elif True:
- buffer.pts = t
- buffer.dts = t
- buffer.duration = (1000*1000*1000)/30.
- return buffer, caps
- def jpeg_src(appsrc):
- def on_need_data(appsrc, data, length):
- name = movie.get_path()
- if name == None or name[-8:] != ".jpeg.sv":
- appsrc.emit("end-of-stream")
- print("End of video stream")
- return
- t = movie.get_time()
- #print("Video: ", name, t)
- buffer, caps = jpeg_read(name, t)
- os.unlink(name)
- appsrc.set_property("caps", caps)
- appsrc.emit("push-buffer", buffer)
- appsrc.set_property("format", Gst.Format.TIME)
- appsrc.set_property("is-live", False)
- appsrc.set_property("block", True)
- name = movie.get_path()
- buffer, caps = jpeg_read(name, 0)
- appsrc.set_property("caps", caps)
- #appsrc.emit("push-buffer", buffer)
- s = appsrc.connect("need-data", on_need_data, "")
- print("Connect", s)
- def v_src(appsrc):
- if not use_jpeg:
- grwsrc(appsrc)
- else:
- jpeg_src(appsrc)
- count = 0
- path = mega_dir
- if use_jpeg:
- movie = grwJPEG(path)
- else:
- movie = grwVideo(path)
- audio = grwAudio(path)
- t1 = movie.get_time()
- t2 = audio.get_time()
- tm = min(t1,t2)
- print("Time base is", tm)
- movie.start_time = tm
- audio.start_time = tm
- def pipeline_video():
- if True:
- s = "appsrc name=source"
- if use_jpeg:
- s += " ! jpegdec "
- else:
- s = "videotestsrc"
- s += " ! video/x-raw,width=(int)640,height=(int)480,format=(string)RGB "
- if False:
- s += " ! videoconvert ! jpegenc"
- s += " ! appsink name=sink"
- elif True:
- s += " ! videoconvert ! autovideosink"
- else:
- s += " ! videoconvert ! x264enc bitrate=3072 speed-preset=ultrafast ! matroskamux ! filesink location=" + out_file
- pipeline = Gst.parse_launch(s)
- p = pipeline.get_by_name("source")
- if p:
- if False:
- mysrc(p)
- else:
- v_src(p)
- p = pipeline.get_by_name("sink")
- if p:
- mysink(p)
- return pipeline
- def pipeline_audio():
- # audiotestsrc ! audioconvert ! audioresample ! autoaudiosink
- if True:
- s = "appsrc name=source"
- else:
- s = "audiotestsrc"
- if True:
- s += " ! audiobuffersplit ! audioconvert ! audioresample ! autoaudiosink"
- else:
- s += " ! ! ! "
- pipeline = Gst.parse_launch(s)
- p = pipeline.get_by_name("source")
- if p:
- sa_src(p)
- p = pipeline.get_by_name("sink")
- if p:
- mysink(p)
- return pipeline
- def pipeline_both():
- if True:
- s = "appsrc name=asrc"
- else:
- s = "audiotestsrc"
- # Audiobuffersplit creates problems with A/V synchronization, avoid.
- #s += "! audiobuffersplit"
- s += " ! audioconvert ! vorbisenc ! mux. "
- if True:
- s += "appsrc name=vsrc"
- if use_jpeg:
- s += " ! jpegdec "
- else:
- s += "videotestsrc"
- s += " ! video/x-raw,width=(int)640,height=(int)480,format=(string)RGB "
- s += " ! videoconvert ! x264enc bitrate=3072 speed-preset=ultrafast ! matroskamux name=mux"
- if False:
- s += " ! decodebin ! playsink"
- else:
- s += " ! filesink location="+out_file
- pipeline = Gst.parse_launch(s)
- p = pipeline.get_by_name("asrc")
- if p:
- sa_src(p)
- p = pipeline.get_by_name("vsrc")
- if p:
- v_src(p)
- return pipeline
- Gst.init(None)
- Gst.debug_set_default_threshold(Gst.DebugLevel.WARNING)
- if False:
- Gst.debug_set_default_threshold(Gst.DebugLevel.INFO)
- if False:
- pipeline = pipeline_video()
- elif False:
- pipeline = pipeline_audio()
- else:
- pipeline = pipeline_both()
- # Function to handle end of stream
- def on_eos(bus, message):
- print("End of stream")
- pipeline.set_state(Gst.State.NULL)
- loop.quit()
- # Set up bus to handle messages
- bus = pipeline.get_bus()
- bus.add_signal_watch()
- bus.connect("message::eos", on_eos)
- # Set the pipeline to the playing state
- pipeline.set_state(Gst.State.PLAYING)
- # Run the main loop to handle GStreamer events
- loop = GLib.MainLoop()
- try:
- loop.run()
- except KeyboardInterrupt:
- pipeline.set_state(Gst.State.NULL)
- loop.quit()
- class Mpegize:
- base = '/tmp/delme.'
- fps = 30.5
- def prepare(m):
- m.source = m.base+'sm'
- m.work = m.base+'smt'
- m.output = m.base+'smo'
- def prepare_work(m):
- m.prepare()
- if not os.path.exists(m.output):
- os.mkdir(m.output)
- if not os.path.exists(m.work):
- os.mkdir(m.work)
- os.chdir(m.work)
- os.system("rm *.jpeg output.*")
- def prepare_source(m):
- m.prepare()
- m.out_index = 0
- l = os.listdir(m.source)
- print("Have", m.display_frames(len(l)), "frames")
- l.sort()
- m.frames = l
- m.unused_frames = copy.deepcopy(l)
- def parse_frame(m, n):
- if n[-5:] != ".mark" and n[-3:] != ".sa" and n[-3:] != ".sv":
- return "", "", 0,
- s = n.split(".")
- i = int(s[0])
- return s[2], s[1], i
- def help(m):
- print("mpegize command base-dir destination-movie fps dng|grw")
- def cleanup(m):
- os.rmdir(m.base+"/sm/")
- os.rmdir(m.base)
- print("Message: Rec")
- sys.stdout.flush()
- def run(m, argv):
- if len(argv) > 2:
- m.base = argv[2]
- mode = argv[1]
- fps = argv[4]
- ext = argv[5]
- if mode == "start":
- print("Phase 0: start, mode ", ext, file=sys.stderr)
- if ext!="grw":
- return
- print("Phase 0: wait", file=sys.stderr)
-
- print("Message: W1")
- sys.stdout.flush()
-
- time.sleep(1)
- print("Phase 1: parallel fun", file=sys.stderr)
-
- print("Message: proc")
- sys.stdout.flush()
- gst_convert(m.base, argv[3], argv[4]=="dng")
- m.cleanup()
- return
- if mode == "convert" or mode == "stop":
- if ext=="grw":
- return
- print("Phase 1: jpegize", file=sys.stderr)
- print("Message: 0%%")
- sys.stdout.flush()
- m.prepare()
- m.jpegize()
- print("Phase 2: mpegize -- ", argv[3], file=sys.stderr)
- print("Message: enc")
- sys.stdout.flush()
- gst_convert(m.base, argv[3], argv[4]=="dng")
- m.cleanup()
- return
- if mode == "gaps":
- print("Video gaps")
- m.stat_gaps("sv")
- print("Audio gaps")
- m.stat_gaps("sa")
- return
- if mode == "jpegize":
- m.prepare()
- m.jpegize()
- return
- m.help()
- def stat_gaps(m, e):
- m.prepare_source()
- last = 0
- num = 0
- total = 0
- limit = 1000000 / m.fps + 15000
- for n in m.frames:
- ext, mid, i = m.parse_frame(n)
- if ext != e:
- continue
- if i - last > limit:
- print("Gap at", i, (i - last) / 1000., "msec")
- num += 1
- last = i
- total += 1
- print("Total", num, "gaps of", total)
- print("Expected", (1000000 / m.fps) / 1000., "msec, limit", limit / 1000., "msec")
- def display_usec(m, v):
- return "%.2f sec" % (v/1000000.)
- def display_frames(m, v):
- return "%d frames %s" % (v, m.display_usec(v * 1000000 / 30.))
- def jpegize(m):
- i = 0
- os.chdir(m.base)
- l = os.listdir(m.base)
- l = filter(lambda n: n[-4:] == ".dng", l)
- l = list(l)
- l.sort()
- print("Have", m.display_frames(len(l)), "dngs")
- for n in l:
- if n[-4:] != ".dng":
- print("Something went terribly wrong")
- continue
- i += 1
- print("Message: %.0f%%" % ((100*i) / len(l)))
- sys.stdout.flush()
- base = n[:-4]
- subprocess.run(['dcraw',
- '-w', # -w Use camera white balance
- '+M', # +M use embedded color matrix
- '-H', '2', # -H 2 Recover highlights by blending them
- '-o', '1', # -o 1 Output in sRGB colorspace
- '-q', '0', # -q 0 Debayer with fast bi-linear interpolation
- '-f', # -f Interpolate RGGB as four colors
- '-T', n]) # -T Output TIFF
- subprocess.run(['convert', base+'.tiff', base+'.jpeg'])
- os.unlink(base+'.tiff')
- os.rename(base+'.jpeg', m.source+"/"+base+'.jpeg.sv')
- m = Mpegize()
- m.run(sys.argv)
|