Using MoviePy to fix unfinalized .flv video - python

While recording my screen with OBS capture, I accumulated large quantity of videos that had been subject to a forced system shutdown, leaving them unfinalized. The videos were created using an .flv format, so when I play them in VLC Player they play flawlessly, however they are missing an end time (video length). Instead, the videos show the running time as they play, but maintain the 00:00 end time, despite the actual video playing for several minutes.
From my understanding, unlike .mp4 formatting, .flv formatted video should be able to be recovered if it has not been finalized (as in the case of my footage stopped by unexpected shutdowns). Since I have a large quantity of unfinalized, I need an automated solution to fix them.
Using MoviePy write_videofile
I attempted to fix the videos by using the MoviePy write_videofile command in the python shell with the directory set to the directory of the bad video:
from moviepy.editor import * #no error
vid = VideoFileClip("oldVideoName.flv") #no error
vid.write_videofile("corrected.mp4") #IndexError
The final line created breifly created a file "correctedTEMP_MPY_wvf_snd.mp3"(only 1KB, unplayable in Audacity), shorty before throwing an exception. I recieved a massive traceback with the final teir reading:
File "\Python37-32\lib\site-packages\moviepy\audio\io\readers.py", line 168, in get_frame
"Accessing time t=%.02f-%.02f seconds, "%(tt[0], tt[-1])+
IndexError: index 0 is out of bounds for axis 0 with size 0
I assumed that this was caused by a problem with an audio reader not accepting the supposed 00:00 timestamp as the length of the video.
Using MoviePy subclip
I attempted to see if there was a way that I could manually feed MoviePy the start and end timestamps, using the subclip method. I know the video is at least 4 seconds long, so I used that as a control test:
clip = vid.subclip("00:00:00", "00:00:05") #no error
clip.write_videofile("corrected.mp4") #OSError
The write_videofile method again threw an exception:
File "\Python37-32\lib\site-packages\moviepy\audio\io\readers.py", line 169, in get_frame
"with clip duration=%d seconds, "%self.duration)
OSError: Error in file oldVideoName.flv,
Accessing time t=0.00-0.04 seconds, with clip duration=0 seconds,
Even if this method were to work, I would need to find a way to automate the process of discovering the video end time.
Using OpenCV CAP_PROP_FRAME_COUNT
One possible solution to finding the end time (video length) is to use cv2, per this post.
import cv2 #no error
vid=cv2.VideoCapture("oldVideoName.flv") #no error
vid.get(cv2.CAP_PROP_FRAME_COUNT) #returns -5.534023222112865e+17
I was not expecting to receive a negative float for this value. Further tests reveal to me that this float does not correspond at all with the length of the video, as all unfinalized videos return the same float for this request. (Normal videos do return their length for this method call) This is useful to iterate over a directory identifying unfinalized videos.
Is using MoviePy to correct a large quantity of unfinalized videos a viable or even possible solution? Is it better to use cv2 (Python OpenCV) for solving this problem?

I was able to fix the video files using yamdi, an open source metadata injector for FLV files. After downloading and installing yamdi, I can use the following command to repair an .flv file named oldVideoName.flv:
yamdi -i oldVideoName.flv -o corrected.flv
The command leaves oldVideoName.flv untouched, and saves a repaired file as corrected.flv.

Related

MP3 loading using librosa return empty data when start_time metadata is 0

I have a dataset of thousands of bird chirps audios (mp3) and I try to load them using librosa.load()
MP3 files are loaded but, most of the time, resulting data is an empty np.ndarray instead of a np.ndarray filled with floats
Using pydub.utils.mediainfo() I wanted to compare MP3 metadata. This function return information such as sampling_rate, codec, duration, bitrate, start_time, ...
I found out that start_time information was the explanation of failed loadings. Indeed, every file where start_time is 0 are not loaded correctly. At the contrary every file where start_time is over 0 are loaded correctly.
I have no problem listening every single MP3 file using VLC audio player.
Is there anything that can explain this behavior? Is there any solution to make these loadings succeed?
I had the same very specific error. The error message I was getting was "Input signal length=0 is too small to resample from 48000->22050", which was because librosa was loading empty arrays in the same circumstances as you mention.
My workaround for it was to specify a duration parameter, in this case I set it to the full length of the file:
dur = pydub.utils.mediainfo(filepath)["duration"]
data, sr = librosa.load(filepath, duration = math.floor(float(dur)))
This solved the empty arrays for me

Moviepy: Displaying Final file of clip in tkinter window error

I am trying to build a small video editor using a Tkinter GUI and Moviepy.
The start/end times of the desired subclip are entered by the user. The error occurs when the user does not specify the end time (the program uses the duration of the source video as the end time for the sub clip.
source_video = VideoFileClip("sample_video_clip.mp4")
start_time = float(start_time_entry.get())
eg: 6 seconds
The user did not specify the end time so the program uses the duration of source clip using the moviepy duration function
end_time = source_video.duration eg. 56.11 seconds
I then try to display the frames that relate to the these time locations
end_frame_image_array = source_video.get_frame(end_time)
end_frame_image_location = Image.fromarray(end_frame_image_array)
end_frame_image_resized = end_frame_image_location.resize((150,100))
end_frame_image = ImageTk.PhotoImage(end_frame_image_resized)
end_frame_label.config(image=end_frame_image)
The following error appears.
OSError: MoviePy error: failed to read the first frame of video file
C:/Users/.../sample_video_clip.mp4.
That might mean that the file is corrupted. That may also mean that
you are using a deprecated version of FFMPEG. On Ubuntu/Debian for
instance the version in the repos is deprecated. Please update to a
recent version from the website.
If the user enters a valid end time not including manually entering 56.11seconds (the duration of the clip) the frame is displayed.
The start frame is also displayed using similar code and works fine
Edit 1.0: Tried using other .mp4 files and the error continues. I don't think its related to the file being corrupted as I can open the files in the media player and also import them into Adobe premier pro 2015
Edit 2.0: looked at the frame.1 seconds from the end time and the image is displayed (see code change below). Error continues if -.1 is not included
end_frame_image_array = source_video.get_frame((end_time-.1))
Edit 3.0: Manually entered time clips until I got to this error
Source Clip duration (length) = 56.11 seconds
entering 56.038 seconds as the end time generates the clip as desired
entering 56.04 and upwards brings up the error stated above

Count the audio tracks in a movie file with python

I'm trying to get the number of audio tracks in a video file. The video have multiple tracks (like different, selectable languages for the same movie.) So if there are three optional languages for the video, i'd like to get the number 3 in the end, no matter if the audio is in stereo, mono or in 5.1.
So far I tried to do it with moviepy. I found only the function "reader.nchannels", but that counts only the first audio track's left and right channel, so I get the number 2 every time.
The code right now is really simple, it looks like this:
from moviepy.editor import *
from moviepy.audio import *
clip = VideoFileClip(source)
audio_tracks = clip.audio.reader.nchannels
I also tried to get every info from the audio like this:
audio = AudioFileClip(source)
tracks= audio_tracks.reader.infos
The output for this looks like this:
"'audio_found': True, 'audio_fps': 48000}"
tburrows13, thanks for pointing to the right direction.
I was able to get the numbers of audio channels and store it in a variable through a py script. Maybe this is not the most elegant solution, but it works, so here it is, if someone needs it. You have to import "subprocess" and use ffprobe with it. ffprobe comes with ffmpeg.
To get the number of streams the command goes like this:
ffprobe <filename here> -show_entries format=nb_streams
This will give you the number of streams in the file, not just the audios, but the video streams too. There is an option to get the data only for the audio streams, but this was not necessary for my project.
You can call this command through a python script. The command needs to be a string, you can store it in a variable too. To get and store the output of this commmand in an other variable you can use this:
variable = subprocess.check_output(subprocesscommand) # subprocesscommand is the string version of the command wrote above.
If you print out now this variable the output will be something like: b'[FORMAT]\r\nnb_streams=3\r\n[/FORMAT]\r\n'
Now you just need to slice the string value, to get the number of the streams.
Thanks again for your help!

get last frame of a vedio clip and extend for 10 scends using moviepy

I'm using moviepy to edit some video clips. I want to play the last frame of orginal clip for 10 seconds, so that I could display some text on it. I tried to convert last frame to video by using
clip.to_ImageClip(t=clip.duration).set_duration(10)
but failed.
Does anyone konw why? And any possible solutions.
import moviepy.editor as ed
import os
content=ed.VideoFileClip(path)
myclip=content.to_ImageClip(content.duration).set_duration(10)
myclip.write_videofile(path)
MoviePy error:
failed to read the first frame of video file C:\my\test.mp4.
That might mean that the file is corrupted.
That may also mean that you are using a deprecated version of FFMPEG.
On Ubuntu/Debian for instance the version in the repos is deprecated.
Please update to a recent version from the website.
I was too facing the same issue, however, the below code worked for me:
clip = VideoFileClip(path)
frame_t = clip.duration - 1
ext_clip = clip.to_ImageClip(t=frame_t, duration=4)
ext_clip.write_videofile("sample4.mp4", fps=24)

my program reduces music speed by 50% but only in one channel

I am using the wave library in python to attempt to reduce the speed of audio by 50%. I have been successful, but only in the right channel. in the left channel it is a whole bunch of static.
import wave,os,math
r=wave.open(r"C:\Users\A\My Documents\LiClipse Workspace\Audio
compression\Audio compression\aha.wav","r")
w=wave.open(r"C:\Users\A\My Documents\LiClipse Workspace\Audio
compression\Audio compression\ahaout.wav","w")
frames=r.readframes(r.getnframes())
newframes=bytearray()
w.setparams(r.getparams())
for i in range(0,len(frames)-1):
newframes.append(frames[i])
newframes.append(frames[i])
w.writeframesraw(newframes)
why is this? since I am just copying and pasting raw data surely I can't generate static?
edit: I've been looking for ages and I finally found a useful resource for the wave format: http://soundfile.sapp.org/doc/WaveFormat/
If I want to preserve stereo sound, it looks like I need to copy the actual sample width of 4 twice. This is because there are two channels and they take up 4 bytes instead of 2.
`import wave
r=wave.open(r"C:\Users\A\My Documents\LiClipse Workspace\Audio
compression\Audio compression\aha.wav","r")
w=wave.open(r"C:\Users\A\My Documents\LiClipse Workspace\Audio
compression\Audio compression\ahaout.wav","w")
frames=r.readframes(r.getnframes())
newframes=bytearray()
w.setparams(r.getparams())
w.setframerate(r.getframerate())
print(r.getsampwidth())
for i in range(0,len(frames)-4,4):
newframes.append(frames[i])
newframes.append(frames[i+1])
newframes.append(frames[i+2])
newframes.append(frames[i+3])
newframes.append(frames[i])
newframes.append(frames[i+1])
newframes.append(frames[i+2])
newframes.append(frames[i+3])
w.writeframesraw(newframes)`
Edit 2:
Okay I have no idea what drove me to do this but I am already enjoying the freedoms it is giving me. I chose to copy the wav file into memory, edit the copy directly, and write it to an output file. I am incredibly happy with the results. I can import a wav, repeat the audio once, and write it to an output file, in only 0.2 seconds. Reducing the speed by half times now takes only 9 seconds instead of the 30+ seconds with my old code using the wav plugin :) here's the code, still kind of un-optimized i guess but it's better than what it was.
import struct
import time as t
t.clock()
r=open(r"C:/Users/apier/Documents/LiClipse Workspace/audio editing
software/main/aha.wav","rb")
w=open(r"C:/Users/apier/Documents/LiClipse Workspace/audio editing
software/main/output.wav","wb")
rbuff=bytearray(r.read())
def replacebytes(array,bites,stop):
length=len(bites)
start=stop-length
for i in range(start,stop):
array[i]=bites[i-start]
def write(audio):
w.write(audio)
def repeat(audio,repeats):
if(repeats==1):
return(audio)
if(repeats==0):
return(audio[:44])
replacebytes(audio, struct.pack('<I', struct.unpack('<I',audio[40:44])
[0]*repeats), 44)
return(audio+(audio[44:len(audio)-58]*(repeats-1)))
def slowhalf(audio):
buff=bytearray()
replacebytes(audio, struct.pack('<I', struct.unpack('<I',audio[40:44])
[0]*2), 44)
for i in range(44,len(audio)-62,4):
buff.append(audio[i])
buff.append(audio[i+1])
buff.append(audio[i+2])
buff.append(audio[i+3])
buff.append(audio[i])
buff.append(audio[i+1])
buff.append(audio[i+2])
buff.append(audio[i+3])
return(audio[:44]+buff)
rbuff=slowhalf(rbuff)
write(rbuff)
print(t.clock())
I am surprised at how small the code is.
Each of the elements returned by readframes is a single byte, even though the type is int. An audio sample is typically 2 bytes. By doubling up each byte instead of each whole sample, you get noise.
I have no idea why one channel would work, with the code shown in the question it should be all noise.
This is a partial fix. It still intermixes the left and right channel, but it will give you an idea of what will work.
for i in range(0,len(frames)-1,2):
newframes.append(frames[i])
newframes.append(frames[i+1])
newframes.append(frames[i])
newframes.append(frames[i+1])
Edit: here's the code that should work in stereo. It copies 4 bytes at a time, 2 for the left channel and 2 for the right, then does it again to double them up. This will keep the channel data from interleaving.
for i in range(0, len(frames), 4):
for _ in range(2):
for j in range(4):
newframes.append(frames[i+j])

Categories

Resources