How to get the duration of an mp3 file - python-3.x

I tried many methods but did not get the exact length value of an mp3 file.
With moviepy:
audiofile = AudioFileClip(url)
print("duration moviepy: " + str(audiofile.duration))
I get result:
duration moviepy: 183.59
With mutagen:
from mutagen.mp3 import MP3
audio = MP3(url)
print("duration mutagen: " + str(audio.info.length))
I received another value of duration:
duration mutagen: 140.93416666666667
Actual duration value when I open the file using windows media player: 2m49s
I don't know what happens to my audio file, I test a few files from the music website and still get the correct value.
This is my audio file

try pyxsox
I tried to use pysox to the audio file which includes this question post
note: pysox needs SOX cli.
how to use it is like this.
import sox
mp3_path = "YOUR STRANGE MP3 FILEPATH"
length = sox.file_info.duration(mp3_path)
print("duration sec: " + str(length))
print("duration min: " + str(int(length/60)) + ':' + str(int(length%60)))
results are
duration sec: 205.347982
duration min: 3:25
and the others information on that mp3 file's duration.
ID3 information => 2.49
mutagen => 2:20
pysox => 3:25
actural length => 3:26
mutagen seems just to read ID3 information.

Using Mutagen
pip install mutagen
python:
import os
from mutagen.mp3 import MP3
def convert_seconds(seconds):
hours = seconds // 3600
seconds %= 3600
minutes = seconds // 60
seconds %= 60
return "%02d:%02d:%02d" % (hours, minutes, seconds)
path = "Your mp3 files floder."
total_length = 0
for root, dirs, files in os.walk(os.path.abspath(path)):
for file in files:
if file.endswith(".mp3"):
audio = MP3(os.path.join(root, file))
length = audio.info.length
total_length += length
hours, minutes, seconds = convert_seconds(total_length).split(":")
print("total duration: " + str(int(hours)) + ':' + str(int(minutes)) + ':' + str(int(seconds)))

Related

Converting video to images using OpenCV library problem

I have this code which converts .mov videos to images with the specified frequency (e.g. every 30 seconds here).
import cv2
# the path to take the video from
vidcap = cv2.VideoCapture(r"C:\Users\me\Camera_videos\Images_30sec\PIR-206_7.MOV")
def getFrame(sec):
vidcap.set(cv2.CAP_PROP_POS_MSEC,sec*1000)
hasFrames,image = vidcap.read()
if hasFrames:
cv2.imwrite("image"+str(count)+".jpg", image) # save frame as JPG file
return hasFrames
sec = 0
frameRate = 30 #//it will capture image every 30 seconds
count= 1
success = getFrame(sec)
while success:
count = count + 1
sec = sec + frameRate
sec = round(sec, 2)
success = getFrame(sec)
I have no problem with smaller files. A 5min long .mov file for example, produces 11 images as expected (5 x 60 seconds / 30 seconds = about 10 images with the first image taken at 0 seconds).
However, when I tried a bigger file, which is 483 MB and is about 32mins long, I have encountered a problem.
It is expected to generate some 32 x 60/30 = 64 images.
However, it runs and runs generating some 40'000 images until I manually stop the program. it seems to be stuck at one of the last images??
I have uploaded both .mov files to my google drive, if anyone wants to have a look.
small file
https://drive.google.com/file/d/1guKtLgM-vwt-5fG3_suJrhVbtwMSjMQe/view?usp=sharing
large file
https://drive.google.com/file/d/1V_HVRM29qwlsU0vCyWiOuBP-tkjdokul/view?usp=sharing
Can somebody advise on what's going on here?

Time Duration in python

Given two strings :
ex:
start_time="3:00 PM"
Duration="3:10"
Start time is in 12-hour clock format (ending in AM or PM), and duration time
that indicates the number of hours and minutes
Assume that the start times are valid times.The minutes in the duration time will
be a whole number less than 60, but the hour can be any whole number.
I need to add the duration time to the start time and return the result
(WITHOUT ANY USE OF LIBRARIES).
The result should be in 12-hour clock format (ending in AM or PM) indicates the
number of hours and minutes
ex:
start_time = "6:30 PM"
Duration = "205:12"
# Returns: 7:42 AM
I Tried and finally got the required answer but unable to produce correct AM or PM for
the result after addition.
what I Tried:
start_time = "6:30 PM"
Duration = "205:12"
#My answer =7:42
#expected :7:42 AM
Can someone help me with the logic to produce correct AM or PM after addition of start
time and Duration.
def add_time(a,b):
a=a.split()
b=b.split()
be=int(a[0][:a[0].find(':')])
af=int(a[0][a[0].find(':')+1:])
be1 = int(b[0][:b[0].find(':')])
af1 = int(b[0][b[0].find(':') + 1:])
return(((be+be1)//24)+1)
s=be+(be1)%12
p=af+af1
if ((s>12) and (p<60)) :
return(str(s-12)+":"+str(p))
elif ((s<12) and (p>60)) :
f = p-60
if len(str(f))<=1:
return(str(s+1)+":"+str('0'+str(f)))
else:
return (str(s + 1)+":"+(str(f)))
elif ((s<12) and (p<60)) :
return(str(s)+":"+str(p))
elif ((s>12) and (p>60)):
f=p-60
if len(str(f)) <= 1:
return (str((s -12)+1)+":"+('0' + str(f)))
else:
return (str((s -12)+1)+":"+(str(f)))
print(add_time("10:10 PM", "3:30"))
# Returns: 1:40 AM
print(add_time("11:43 PM", "24:20"))
# Returns: 12:03 AM
Your code does not seem to cover all edge cases, e.g. add_time("11:43 PM", "1:20") returns None because the case s==12 is not covered.
Therefore one should put <= instead of < in the respective if conditions. The case where the addition of the minutes leads to hours greater than 12 although the addition of the hours itself did not, is not covered either. So we should check the minutes first and the hours after that instead of simultaneously.
To make the code more readable, we use f-strings and can use str.split() with an argument, forgive me for changing the code quite a bit:
def add_time(a,b):
start = a.split()
start_h, start_m = [int(val) for val in start[0].split(':')]
start_app = start[1]
dur_h, dur_m = [int(val) for val in b.split(':')]
end_m = start_m+dur_m
end_h = end_m//60
end_m %= 60
end_h += start_h+dur_h
if (end_h//12)%2==0:
end_app = start_app
else:
end_app = 'AM' if start_app=='PM' else 'PM'
return f'{end_h:02}:{end_m:02} {end_app}'

How to make TIME subtitles(.srt, .smi etc) with frame unit

I want to create a Python script that make subtitle (a ticking timecode).
I have an h.264 binary file that has timestamp in each frame. so I want to parse this timestamp and make the subtitle with it.
Here's what I've tried with ffmpeg below.
ffmpeg -y -i video.avi -vf "drawtext=fontfile=C:\Windows\Fonts\consolab.ttf: fontsize=12:fontcolor=yellow: box=1:boxcolor=black#0.4: text='TIME\: %{pts\:gmtime\:1561939200}':x=(w-tw)/2:y=(h-th)/2" test.avi
Output .avi is okay with the timestamp, but, I needed to re-encode the source video file which takes a lot of time.
So, I want to use a different way, which is creating subtitles.
Question is, is there any way to make subtitle with frame time? or useful info to fill me in?
The current result:
The expected result:
You can use directly Timecode by defining the starting time :
ffmpeg -i source_file -c:v libx264 -vf drawtext="fontfile=C\\:/Windows/Fonts/arial.ttf:fontsize=45:timecode='10\:00\:00\:00':fontcolor='white':box=1:boxcolor='black':rate=25:x=(w-text_w)/2:y=h/1.2" -c:a aac -f mp4 output_with_TC.mp4
You can also grab timecode from file using ffprobe and use it in a batch file
#echo off
cd C:\bin
set file=%1
For %%A in ("%file%") do (
Set Folder=%%~dpA
Set Name=%%~nA
)
set "CommandLine=ffprobe -i %file% -show_entries format_tags=timecode -of default=noprint_wrappers=1"
setlocal EnableDelayedExpansion
for /F "delims=" %%I in ('!CommandLine!') do set "TC=%%I"
echo.File is: %file%
echo.TC is: %TC%
set hours=%TC:~13,2%
set minutes=%TC:~16,2%
set seconds=%TC:~19,2%
set frames=%TC:~22,2%
set h=!hours: =!
set m=!minutes: =!
set s=!seconds: =!
set f=!frames: =!
set final_TC=%h%:%m%:%s%:%f%
echo.final_TC is : %final_TC%
ffmpeg -i %file% -c:v libx264 -vf drawtext="fontfile=C\\:/Windows/Fonts/arial.ttf:fontsize=45:timecode=\'%final_TC%\':fontcolor='white':box=1:boxcolor='black#0.4':rate=25:x=1600:y=30" -c:a aac -f mp4 output_%Name%.mp4 -y
endlocal
Maybe this code will help you to get a quick solution (I've modified slightly for posting here):
Set the fps variable to your own video's FPS.
set the myTimeNum var by using a timer to increase some frameNum count every FPS interval.
Logic:
Where example FPS is 10...
FPS= 10; frameNum = 0; myTimeNum = 0
Means for 10 times per second (using a timer) you must...
myTimeNum = (1000 / FPS) * frameNum
timecode = smpte.totc(myTimeNum, fps)
frameNum++
Code to try:
fps = 30
myTimeNum = 50000;
timecode = smpte.totc(myTimeNum, fps)
print timecode
#Converts frames to SMPTE timecode of arbitrary frame rate and back.
#For DF calculations use 29.976 frame rate.
#Igor Ridanovic, igor#HDhead.com
def totc(x, fps):
"""Converts frame count to SMPTE timecode."""
spacer = ':'
frHour = fps * 3600
frSec = fps * 60
hr = int(x // frHour)
mn = int((x - hr * frHour) // frSec)
sc = int((x - hr * frHour - mn * frSec) // fps)
fr = int(round(x - hr * frHour - mn * frSec - sc * fps))
return(
str(hr).zfill(2) + spacer +
str(mn).zfill(2) + spacer +
str(sc).zfill(2) + spacer +
str(fr).zfill(2))

Overwrite GPS coordinates in Image Exif using Python 3.6

I am trying to transform image geotags so that images and ground control points lie in the same coordinate system inside my software (Pix4D mapper).
The answer here says:
Exif data is standardized, and GPS data must be encoded using
geographical coordinates (minutes, seconds, etc) described above
instead of a fraction. Unless it's encoded in that format in the exif
tag, it won't stick.
Here is my code:
import os, piexif, pyproj
from PIL import Image
img = Image.open(os.path.join(dirPath,fn))
exif_dict = piexif.load(img.info['exif'])
breite = exif_dict['GPS'][piexif.GPSIFD.GPSLatitude]
lange = exif_dict['GPS'][piexif.GPSIFD.GPSLongitude]
breite = breite[0][0] / breite[0][1] + breite[1][0] / (breite[1][1] * 60) + breite[2][0] / (breite[2][1] * 3600)
lange = lange[0][0] / lange[0][1] + lange[1][0] / (lange[1][1] * 60) + lange[2][0] / (lange[2][1] * 3600)
print(breite) #48.81368778730952
print(lange) #9.954511162420633
x, y = pyproj.transform(wgs84, gk3, lange, breite) #from WGS84 to GaussKrüger zone 3
print(x) #3570178.732528623
print(y) #5408908.20172699
exif_dict['GPS'][piexif.GPSIFD.GPSLatitude] = [ ( (int)(round(y,6) * 1000000), 1000000 ), (0, 1), (0, 1) ]
exif_bytes = piexif.dump(exif_dict) #error here
img.save(os.path.join(outPath,fn), "jpeg", exif=exif_bytes)
I am getting struct.error: argument out of range in the dump method. The original GPSInfo tag looks like: {0: b'\x02\x03\x00\x00', 1: 'N', 2: ((48, 1), (48, 1), (3449322402, 70000000)), 3: 'E', 4: ((9, 1), (57, 1), (1136812930, 70000000)), 5: b'\x00', 6: (3659, 10)}
I am guessing I have to offset the values and encode them properly before writing, but have no idea what is to be done.
It looks like you are already using PIL and Python 3.x, not sure if you want to continue using piexif but either way, you may find it easier to convert the degrees, minutes, and seconds into decimal first. It looks like you are trying to do that already but putting it in a separate function may be clearer and account for direction reference.
Here's an example:
def get_decimal_from_dms(dms, ref):
degrees = dms[0][0] / dms[0][1]
minutes = dms[1][0] / dms[1][1] / 60.0
seconds = dms[2][0] / dms[2][1] / 3600.0
if ref in ['S', 'W']:
degrees = -degrees
minutes = -minutes
seconds = -seconds
return round(degrees + minutes + seconds, 5)
def get_coordinates(geotags):
lat = get_decimal_from_dms(geotags['GPSLatitude'], geotags['GPSLatitudeRef'])
lon = get_decimal_from_dms(geotags['GPSLongitude'], geotags['GPSLongitudeRef'])
return (lat,lon)
The geotags in this example is a dictionary with the GPSTAGS as keys instead of the numeric codes for readability. You can find more detail and the complete example from this blog post: Getting Started with Geocoding Exif Image Metadata in Python 3
After much hemming & hawing I reached the pages of py3exiv2 image metadata manipulation library. One will find exhaustive lists of the metadata tags as one reads through but here is the list of EXIF tags just to save few clicks.
It runs smoothly on Linux and provides many opportunities to edit image-headers. The documentation is also quite clear. I recommend this as a solution and am interested to know if it solves everyone else's problems as well.

Stop time from saying 07 minutes and just 7 in Pyttsx3

import pyttsx3
import time
time = time.strftime("%M minutes past %I")
engine = pyttsx3.init()
engine.setProperty('rate',200)
engine.say("Hi Tom");
engine.say("The time is" + time);
engine.runAndWait();
When running this it will say "Hi Tom, the time is 07 minutes past 10" for example and will say a 0 in front of the minutes if its between 0-9 but 10-59 its says it normally. Is there a way to remove the 0 from being said?
Could this be useful
t = time.localtime()
...
engine.say('The time is %d minutes past %s' % (t.tm_min,t.tm_hour))

Resources