ffmpeg reverts to default subtitle font - python

I'm using ffmpeg-python to burn an SRT into a video file.
My code looks something like this:
caption_file = "captions.srt"
style = "FontName=Roboto-Regular,FontSize=8"
fonts_dir = "fonts-main/apache"
(
ffmpeg
.concat(video.filter("subtitles", caption_file, fontsdir=fonts_dir, force_style=style), audio, v=1, a=1)
.output("my_video_w_subs.mp4")
.run()
)
When I run the code, the SRT indeed gets burned, but not in the specified font (Roboto-Regular).
Here are the output logs:
[Parsed_subtitles_0 # 0x55adfd490e80] Loading font file 'fonts-main/apache/Roboto-Regular.ttf'
[Parsed_subtitles_0 # 0x55adfd490e80] Using font provider fontconfig
[Parsed_subtitles_0 # 0x55adfd490e80] fontselect: (Roboto-Regular, 400, 0) -> /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf, 0, DejaVuSans
It seems the desired font was found and loaded so I'm not sure why wasn't it used.

It looks like fonts_dir should include the font file name.
Instead of fonts_dir = "fonts-main/apache", try:
fonts_dir = "fonts-main/apache/Roboto-Regular.ttf"
Note: fonts-main/apache/Roboto-Regular.ttf uses relative path.
You may try using full path like: /fonts-main/apache/Roboto-Regular.ttf.
For debugging add the argument .global_args('-report'), and check the log file.
Here is a complete code sample:
import ffmpeg
caption_file = "captions.srt"
style = "FontName=Roboto-Regular,FontSize=8"
fonts_dir = "/fonts-main/apache/Roboto-Regular.ttf"
input = ffmpeg.input('1.avi')
video = input.video
audio = input.audio
(
ffmpeg
.concat(video.filter("subtitles", filename=caption_file, fontsdir=fonts_dir, force_style=style), audio, v=1, a=1)
.output("my_video_w_subs.mp4")
.global_args('-report')
.overwrite_output()
.run()
)
When using fonts_dir = "/fonts-main/apache", I am getting an error ass_read_file(/fonts-main/apache/����): fopen failed
Note:
I am using Windows, and I am not sure that the fonts_dir value is the true problem (I am not getting the Loading font file message).

Related

Load font file with Windows api Python

I was looking for a way to load a ttf file and print text with that font. While looking for some information, I found this question: load a ttf font with the Windows API
In that question they recommend adding a private font with AddFontResourceEx. But I didn't find any way to access such function with pywin32 module and ctypes.windll. Is there any way to access this function? Or failing that, another way to print text with a ttf font without using Pillow???
Next, I will leave a code so you can do the tests:
import win32print
import win32gui
import win32ui
hprinter = win32print.OpenPrinter("Microsoft Print to Pdf")
devmode = win32print.GetPrinter(hprinter, 2)["pDevMode"]
hdc = win32gui.CreateDC("WINSPOOL", printer, devmode)
dc = win32ui.CreateDCFromHandle(Self.hdc)
Edit
I managed to access the function with
ctypes.windll.gdi32.AddFontResourceExA
But now I want to access the FR_PRIVATE constant. How can I do it?
Edit 2
I found out that the function doesn't work even without that constant.
I adapted the code from this answer and got the answer!
I will put the code below:
def add_font_file(file):
FR_PRIVATE = 0x10
file = ctypes.byref(ctypes.create_unicode_buffer(file))
font_count = gdi32.AddFontResourceExW(file, FR_PRIVATE, 0)
if(font_count == 0):
raise RuntimeError("Error durante la carga de la fuente.")
In case the original link goes down, the original code was as follows:
from ctypes import windll, byref, create_unicode_buffer, create_string_buffer
FR_PRIVATE = 0x10
FR_NOT_ENUM = 0x20
def loadfont(fontpath, private=True, enumerable=False):
'''
Makes fonts located in file `fontpath` available to the font system.
`private` if True, other processes cannot see this font, and this
font will be unloaded when the process dies
`enumerable` if True, this font will appear when enumerating fonts
See https://msdn.microsoft.com/en-us/library/dd183327(VS.85).aspx
'''
# This function was taken from
# https://github.com/ifwe/digsby/blob/f5fe00244744aa131e07f09348d10563f3d8fa99/digsby/src/gui/native/win/winfonts.py#L15
# This function is written for Python 2.x. For 3.x, you
# have to convert the isinstance checks to bytes and str
if isinstance(fontpath, str):
pathbuf = create_string_buffer(fontpath)
AddFontResourceEx = windll.gdi32.AddFontResourceExA
elif isinstance(fontpath, unicode):
pathbuf = create_unicode_buffer(fontpath)
AddFontResourceEx = windll.gdi32.AddFontResourceExW
else:
raise TypeError('fontpath must be of type str or unicode')
flags = (FR_PRIVATE if private else 0) | (FR_NOT_ENUM if not enumerable else 0)
numFontsAdded = AddFontResourceEx(byref(pathbuf), flags, 0)
return bool(numFontsAdded)

Why do I only get the Desktop wallpaper when trying to take a screenshot on a Mac?

I'm trying to take a screenshot with python. But every option I try only seems to return the desktop wallpaper and not the programs on top.
Here's a run through of what I've tried and how I'm doing it.
First I used autopy to try and get pixels from the screen using autopy.color.hex_to_rgb(autopy.screen.get_color(x, y)). But it was only telling me the pixels of the desktop background.
Then I tried PIL(Pillow) using this code:
from PIL import ImageGrab
im = ImageGrab.grab()
im.save('screenshot.png')
This only returned the desktop wallpaper.
Finally, I've tried using this script which I found on this thread.
import Quartz
import LaunchServices
from Cocoa import NSURL
import Quartz.CoreGraphics as CG
def screenshot(path, region = None):
"""region should be a CGRect, something like:
>>> import Quartz.CoreGraphics as CG
>>> region = CG.CGRectMake(0, 0, 100, 100)
>>> sp = ScreenPixel()
>>> sp.capture(region=region)
The default region is CG.CGRectInfinite (captures the full screen)
"""
if region is None:
region = CG.CGRectInfinite
# Create screenshot as CGImage
image = CG.CGWindowListCreateImage(
region,
CG.kCGWindowListOptionOnScreenOnly,
CG.kCGNullWindowID,
CG.kCGWindowImageDefault)
dpi = 72 # FIXME: Should query this from somewhere, e.g for retina displays
url = NSURL.fileURLWithPath_(path)
dest = Quartz.CGImageDestinationCreateWithURL(
url,
LaunchServices.kUTTypePNG, # file type
1, # 1 image in file
None
)
properties = {
Quartz.kCGImagePropertyDPIWidth: dpi,
Quartz.kCGImagePropertyDPIHeight: dpi,
}
# Add the image to the destination, characterizing the image with
# the properties dictionary.
Quartz.CGImageDestinationAddImage(dest, image, properties)
# When all the images (only 1 in this example) are added to the destination,
# finalize the CGImageDestination object.
Quartz.CGImageDestinationFinalize(dest)
if __name__ == '__main__':
# Capture full screen
screenshot("/tmp/testscreenshot_full.png")
# Capture region (100x100 box from top-left)
region = CG.CGRectMake(0, 0, 100, 100)
screenshot("/tmp/testscreenshot_partial.png", region=region)
Again it returns my desktop wallpaper.
Why is it doing this? How can I get a screenshot in the same way I would if I were to press 'cmd + shift + 3'.
This is a privacy feature, macOS prevents arbitrary apps from viewing the contents of other app windows. To get permission, your app will need access to the screen recording permission in macOS preferences.
Since this is a Python script, I think the app you're running the script from needs to be given permission, so either Terminal or whatever IDE you're using.

Save video instead of saving images while using basler camera and python

I'm using Basler camera and python to record some video. I can successfully capture individual frames, but I don't know how to record a video.
Following is my code:
import os
import pypylon
from imageio import imwrite
import time
start=time.time()
print('Sampling rate (Hz):')
fsamp = input()
fsamp = float(fsamp)
time_exposure = 1000000*(1/fsamp)
available_cameras = pypylon.factory.find_devices()
cam = pypylon.factory.create_device(available_cameras[0])
cam.open()
#cam.properties['AcquisitionFrameRateEnable'] = True
#cam.properties['AcquisitionFrameRate'] = 1000
cam.properties['ExposureTime'] = time_exposure
buffer = tuple(cam.grab_images(2000))
for count, image in enumerate(buffer):
filename = str('I:/Example/{}.png'.format(count))
imwrite(filename, image)
del buffer
I haven't found a way to record a video using pypylon; it seems to be a pretty light wrapper around Pylon. However, I have found a way to save a video using imageio:
from imageio import get_writer
with get_writer('I:/output-filename.mp4', fps=fps) as writer:
# Some stuff with the frames
The above can be used with .mov, .avi, .mpg, .mpeg, .mp4, .mkv or .wmv, so long as the FFmpeg program is available. How you will install this program depends on your operating system. See this link for details on the parameters you can use.
Then, simply replace the call to imwrite with:
writer.append_data(image)
ensuring that this occurs in the with block.
An example implementation:
import os
import pypylon
from imageio import get_writer
while True:
try:
fsamp = float(input('Sampling rate (Hz): '))
break
except ValueError:
print('Invalid input.')
time_exposure = 1000000 / fsamp
available_cameras = pypylon.factory.find_devices()
cam = pypylon.factory.create_device(available_cameras[0])
cam.open()
cam.properties['ExposureTime'] = time_exposure
buffer = tuple(cam.grab_images(2000))
with get_writer(
'I:/output-filename.mkv', # mkv players often support H.264
fps=fsamp, # FPS is in units Hz; should be real-time.
codec='libx264', # When used properly, this is basically
# "PNG for video" (i.e. lossless)
quality=None, # disables variable compression
pixelformat='rgb24', # keep it as RGB colours
ffmpeg_params=[ # compatibility with older library versions
'-preset', # set to faster, veryfast, superfast, ultrafast
'fast', # for higher speed but worse compression
'-crf', # quality; set to 0 for lossless, but keep in mind
'11' # that the camera probably adds static anyway
]
) as writer:
for image in buffer:
writer.append_data(image)
del buffer

Play mp4 video with python and gstreamer

I'm trying to play video in mp4 format but not working.
In console I execute this line and it works:
gst-launch playbin uri=rtmp://localhost:1935/files/video.mp4
But if I change to version 1.0 only works the audio:
gst-launch-1.0 playbin uri=rtmp://localhost:1935/files/video.mp4
in python I have the following code:
self.player = Gst.Pipeline.new("player")
source = Gst.ElementFactory.make("filesrc", "file-source")
demuxer = Gst.ElementFactory.make("mp4mux", "demuxer")
demuxer.connect("pad-added", self.demuxer_callback)
self.video_decoder = Gst.ElementFactory.make("x264enc", "video-decoder")
self.audio_decoder = Gst.ElementFactory.make("vorbisdec", "audio-decoder")
audioconv = Gst.ElementFactory.make("audioconvert", "converter")
audiosink = Gst.ElementFactory.make("autoaudiosink", "audio-output")
videosink = Gst.ElementFactory.make("autovideosink", "video-output")
self.queuea = Gst.ElementFactory.make("queue", "queuea")
self.queuev = Gst.ElementFactory.make("queue", "queuev")
colorspace = Gst.ElementFactory.make("videoconvert", "colorspace")
self.player.add(source)
self.player.add(demuxer)
self.player.add(self.video_decoder)
self.player.add(self.audio_decoder)
self.player.add(audioconv)
self.player.add(audiosink)
self.player.add(videosink)
self.player.add(self.queuea)
self.player.add(self.queuev)
self.player.add(colorspace)
source.link(demuxer)
self.queuev.link(self.video_decoder)
self.video_decoder.link(colorspace)
colorspace.link(videosink)
self.queuea.link(self.audio_decoder)
self.audio_decoder.link(audioconv)
audioconv.link(audiosink)
but I get this error:
Error: Error in the internal data flow. gstbasesrc.c(2865): gst_base_src_loop (): /GstPipeline:player/GstFileSrc:file-source:
streaming task paused, reason not-linked (-1)
What can be happening? think I am no good decoding
You are missing linking the demuxer pads to your queues. Demuxers have 'sometimes' pads so you need to listen to the pad-added signal of them and link in this callback. Remember to check the pad caps once you get them and link to the appropriate branch of your pipeline.
You can read about dynamic pads here: http://gstreamer.freedesktop.org/data/doc/gstreamer/head/manual/html/chapter-pads.html#section-pads-dynamic
You have in your code:
demuxer = Gst.ElementFactory.make("mp4mux", "demuxer")
demuxer.connect("pad-added", self.demuxer_callback)
I hope this is a cut/paste error, as demuxing with a mux will not work. I believe for an .mp4 file, the normal demuxer (if you are choosing one by hand) is qtdemux.
You could also use decodebin to decode the file for you.

Python + eyeD3: cannot save date to mp3 metadata

I am trying to update metadata of a bunch of mp3 files using Python and its eyeD3 API.
It looks fairly simple, code I'm using looks as follows:
if not eyeD3.isMp3File(filename):
print filename, 'is not a mp3 file. Ignoring it.'
tag = eyeD3.Tag()
tag.link(filename)
tag.setVersion(eyeD3.ID3_V2)
tag.setTextEncoding(eyeD3.UTF_8_ENCODING)
tag.setTitle(dataset['Title'])
tag.setDate(datetime.datetime.now().year)
tag.update()
What happens is: code executes silently (no errors or exceptions), title is set correctly, date is not set in target file. It remains empty or set to previous value (checked both cases).
Help for setDate function is not particulary amusing:
setDate(self, year, month=None, dayOfMonth=None, hour=None, minute=None, second=None, fid=None) unbound eyeD3.tag.Tag method
... but tells me that my call should be ok. Any ideas what's happening here?
I got the same question as yours. Finally, I abandoned the eyeD3 lib, mutagen is a good option.
Here is my example using mutagen.mp3 in Python.
from mutagen.mp3 import MP3
from mutagen.id3 import ID3, APIC, TIT2, TPE1, TRCK, TALB, USLT, error
# ID3 info:
# APIC: picture
# TIT2: title
# TPE1: artist
# TRCK: track number
# TALB: album
# USLT: lyric
def id3_cook(directory, filename, item, track_num):
pic_file = directory + '/cover.jpg' # pic file
audio = MP3(filename, ID3=ID3)
try:
audio.add_tags()
except:
pass
audio.tags.add(APIC(
encoding=3,
mime='image/jpeg',
type=3,
desc=u'Cover Picture',
data=open(pic_file).read()
))
audio.tags.add(TIT2(encoding=3, text=item['song'].decode('utf-8')))
audio.tags.add(TALB(encoding=3, text=item['album'].decode('utf-8')))
audio.tags.add(TPE1(encoding=3, text=item['artist'].decode('utf-8')))
audio.tags.add(TRCK(encoding=3, text=str(track_num).decode('utf-8')))
audio.tags.add(USLT(encoding=3, lang=u'eng', desc=u'desc', text=item['lyric'].decode('utf-8')))
audio.save()

Categories