Python + eyeD3: cannot save date to mp3 metadata - python

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()

Related

Mp3 duration doubling after mutagen.py edits in iTunes only

I am trying to create a python code that edits a mp3's info and adds the title, artist, and album. This will make it so that when it's opened in iTunes, I don't have to manually add the info to the song. After I open the mp3 file in iTunes, the duration of the mp3 that iTunes shows has doubled (to 7:07), while the duration of the mp3 file in File Explorer and VLC stays the same (3:39). I am 90% sure that the length doubles after mutagen(the module that edits the file) does its work. But it could also be iTunes problem, I am not sure. Please provide feedback, thank you!
https://discussions.apple.com/thread/250712483 - This thread says that after editing the artwork of a song, track times are extended.
Code right now:
def info_of_mp3(dictionary_of_info, filenamefull):
from mutagen.id3 import ID3, TIT2, TPE2, TALB, TPE1, TYER, TDAT, TRCK, TCON, TORY, TPUB, USLT
dictionary_of_info = { # example info input
"TIT2":"Idli Chutney", #TITLE
"TPE1":"Sean Roldan", #ARTIST
"TALB":"None" #ALBUM
}
filenamefull = "Sean Roldan - Idli Chutney.mp3" #example filename input
SAVE_PATH = "C:/directions/to/my/folder"
audio = ID3(SAVE_PATH+"/"+filenamefull)
audio.add(TIT2(encoding=3, text = dictionary_of_info["TIT2"])) #TITLE
audio.add(TPE1(encoding=3, text = dictionary_of_info["TPE1"])) #ARTIST
audio.add(TALB(encoding=3, text = dictionary_of_info["TALB"])) #ALBUM
audio.save(v2_version=3)
File Explorer length:
File Explorer
iTunes length:
iTunes
You just have to convert to AAC version on iTunes. This also makes a copy of the song, so you can delete the original.

Using Unittest/PyTest/etc. to test a Python function that works with files

I have a function that takes in a path to an .mp3 or .m4a file and returns a string containing information extracted from the file using TinyTag (import statement removed for brevity):
def create_search_term(path_to_song):
tag = TinyTag.get(path_to_song)
artist = tag.artist
album = tag.album
search_term = f"{album} {artist} Album Cover"
return search_term
I've tested this manually, and it works with a real MP3 file path on my computer. However, I'm trying to add unit tests to this project. Is there a best practice to go writing a unit test for such a function?
A sample use case would be "/path/to/bruce/springsteen/born_to_run.mp3" taken as an input, and "Born to Run Album Cover" as a return value.
Patch out TinyTag to produce a sample response, and then test the function result against that response:
from mock import patch
def test_create_search_term():
with patch("__main__.TinyTag") as mock_tinytag:
mock_tinytag.get.return_value.artist = "Bruce Springsteen"
mock_tinytag.get.return_value.album = "Born to Run"
assert create_search_term("foo") == "Born to Run Bruce Springsteen Album Cover"

Adding a pause in Google-text-to-speech

I am looking for a small pause, wait, break or anything that will allow for a short break (looking for about 2 seconds +-, configurable would be ideal) when speaking out the desired text.
People online have said that adding three full stops followed by a space creates a break but I don't seem to be getting that. Code below is my test that has no pauses, sadly.. Any ideas or suggestions?
Edit: It would be ideal if there is some command from gTTS that would allow me to do this, or maybe some trick like using the three full stops if that actually worked.
from gtts import gTTS
import os
tts = gTTS(text=" Testing ... if there is a pause ... ... ... ... ... longer pause? ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... insane pause " , lang='en', slow=False)
tts.save("temp.mp3")
os.system("temp.mp3")
Ok, you need Speech Synthesis Markup Language (SSML) to achieve this.
Be aware you need to setting up Google Cloud Platform credentials
first in the bash:
pip install --upgrade google-cloud-texttospeech
Then here is the code:
import html
from google.cloud import texttospeech
def ssml_to_audio(ssml_text, outfile):
# Instantiates a client
client = texttospeech.TextToSpeechClient()
# Sets the text input to be synthesized
synthesis_input = texttospeech.SynthesisInput(ssml=ssml_text)
# Builds the voice request, selects the language code ("en-US") and
# the SSML voice gender ("MALE")
voice = texttospeech.VoiceSelectionParams(
language_code="en-US", ssml_gender=texttospeech.SsmlVoiceGender.MALE
)
# Selects the type of audio file to return
audio_config = texttospeech.AudioConfig(
audio_encoding=texttospeech.AudioEncoding.MP3
)
# Performs the text-to-speech request on the text input with the selected
# voice parameters and audio file type
response = client.synthesize_speech(
input=synthesis_input, voice=voice, audio_config=audio_config
)
# Writes the synthetic audio to the output file.
with open(outfile, "wb") as out:
out.write(response.audio_content)
print("Audio content written to file " + outfile)
def text_to_ssml(inputfile):
raw_lines = inputfile
# Replace special characters with HTML Ampersand Character Codes
# These Codes prevent the API from confusing text with
# SSML commands
# For example, '<' --> '<' and '&' --> '&'
escaped_lines = html.escape(raw_lines)
# Convert plaintext to SSML
# Wait two seconds between each address
ssml = "<speak>{}</speak>".format(
escaped_lines.replace("\n", '\n<break time="2s"/>')
)
# Return the concatenated string of ssml script
return ssml
text = """Here are <say-as interpret-as="characters">SSML</say-as> samples.
I can pause <break time="3s"/>.
I can play a sound"""
ssml = text_to_ssml(text)
ssml_to_audio(ssml, "test.mp3")
More documentation:
Speaking addresses with SSML
But if you don't have Google Cloud Platform credentials, the cheaper and easier way is to use time.sleep(1) method
If there is any background waits required, you can use the time module to wait as below.
import time
# SLEEP FOR 5 SECONDS AND START THE PROCESS
time.sleep(5)
Or you can do a 3 time check with wait etc..
import time
for tries in range(3):
if someprocess() is False:
time.sleep(3)
You can save multiple mp3 files, then use time.sleep() to call each with your desired amount of pause:
from gtts import gTTS
import os
from time import sleep
tts1 = gTTS(text="Testingn" , lang='en', slow=False)
tts2 = gTTS(text="if there is a pause" , lang='en', slow=False)
tts3 = gTTS(text="insane pause " , lang='en', slow=False)
tts1.save("temp1.mp3")
tts2.save("temp2.mp3")
tts3.save("temp3.mp3")
os.system("temp1.mp3")
sleep(2)
os.system("temp2.mp3")
sleep(3)
os.system("temp3.mp3")
Sadly the answer is no, gTTS package has no additional function for pause,an issue already been created in 2018 for adding a pause function ,but it is smart enough to add natural pauses by tokenizer.
What is tokenizer?
Function that takes text and returns it split into a list of tokens (strings). In the gTTS context, its goal is
to cut the text into smaller segments that do not exceed the maximum character size allowed(100) for each TTS API
request, while making the speech sound natural and continuous. It does so by splitting text where speech would
naturaly pause (for example on ".") while handling where it should not (for example on “10.5” or “U.S.A.”).
Such rules are called tokenizer cases, which it takes a list of.
Here is an example:
text = "regular text speed no pause regular text speed comma pause, regular text speed period pause. regular text speed exclamation pause! regular text speed ellipses pause... regular text speed new line pause \n regular text speed "
So in this case, adding a sleep() seems like the only answer. But tricking the tokenizer is worth mentioning.
You can add arbitrary pause with Pydub by saving and concatenating temporary mp3. Then you can use a silent audio for your pause.
You can use any break point symbols of your choice where you want to add pause (here $):
from pydub import AudioSegment
from gtts import gTTS
contents = "Hello with $$ 2 seconds pause"
contents.split("$") # I have chosen this symbol for the pause.
pause2s = AudioSegment.from_mp3("silent.mp3")
# silent.mp3 contain 2s blank mp3
cnt = 0
for p in parts:
# The pause will happen for the empty element of the list
if not p:
combined += pause2s
else:
tts = gTTS(text=p , lang=langue, slow=False)
tmpFileName="tmp"+str(cnt)+".mp3"
tts.save(tmpFileName)
combined+=AudioSegment.from_mp3(tmpFileName)
cnt+=1
combined.export("out.mp3", format="mp3")
Late to the party here, but you might consider trying out the audio_program_generator package. You provide a text file comprised of individual phrases, each of which has a configurable pause at the end. In return, it gives you an mp3 file that 'stitches together' all the phrases and their pauses into one continuous audio file. You can optionally mix in a background sound-file, as well. And it implements several of the other bells and whistles that Google TTS provides, like accents, slow-play-speech, etc.
Disclaimer: I am the author of the package.
I had the same problem, and didn't want to use lots of temporary files on disk. This code parses an SSML file, and creates silence whenever a <break> tag is found:
import io
from gtts import gTTS
import lxml.etree as etree
import pydub
ssml_filename = 'Section12.35-edited.ssml'
wav_filename = 'Section12.35-edited.mp3'
events = ('end',)
DEFAULT_BREAK_TIME = 250
all_audio = pydub.AudioSegment.silent(100)
for event, element in etree.iterparse(
ssml_filename,
events=events,
remove_comments=True,
remove_pis=True,
attribute_defaults=True,
):
tag = etree.QName(element).localname
if tag in ['p', 's'] and element.text:
tts = gTTS(element.text, lang='en', tld='com.au')
with io.BytesIO() as temp_bytes:
tts.write_to_fp(temp_bytes)
temp_bytes.seek(0)
audio = pydub.AudioSegment.from_mp3(temp_bytes)
all_audio = all_audio.append(audio)
elif tag == 'break':
# write silence to the file.
time = element.attrib.get('time', None) # Shouldn't be possible to have no time value.
if time:
if time.endswith('ms'):
time_value = int(time.removesuffix('ms'))
elif time.endswith('s'):
time_value = int(time.removesuffix('s')) * 1000
else:
time_value = DEFAULT_BREAK_TIME
else:
time_value = DEFAULT_BREAK_TIME
silence = pydub.AudioSegment.silent(time_value)
all_audio = all_audio.append(silence)
with open(wav_filename, 'wb') as output_file:
all_audio.export(output_file, format='mp3')
I know 4Rom1 used this method above, but to put it more simply, I found this worked really well for me. Get a 1 sec silent mp3, I found one by googling 1 sec silent mp3. Then use pydub to add together audio segments however many times you need. For example to add 3 seconds of silence
from pydub import AudioSegment
seconds = 3
output = AudioSegment.from_file("yourfile.mp3")
output += AudioSegment.from_file("1sec_silence.mp3") * seconds
output.export("newaudio.mp3", format="mp3")

I need a super-duper simple CGI Python photo upload

I've looked through tons of answers but the truth is, I only know super basic python and I really need help. I don't know the os module or anything like that and I can't use PHP (not that I know it anyway, but it's not permitted) and I need something so easy that I can understand it.
Basically, I need a CGI upload (I don't need the HTML form, I've got that much down) that will take the photo and save it. That's it. I don't need any fancy place for it to save, I just need the file to be properly uploaded from the form.
I've got various versions of this function and I can't get them working because I don't understand them so PLEASE HELP!!!
import cgi
def savefile (filename, photodoc):
form=cgi.FieldStorage()
name=form[filename]
period=name.split(.)
if period[1]=="jpeg" or period[1]=="jpg" or period[1]=="png":
idk what to do
else:
make an error message
This cgi program will "take the photo and save it. That's it."
#!/usr/bin/python2.7
import cgi
field=cgi.FieldStorage()['fieldname']
open(field.filename, 'wb').write(field.value)
Among the things it doesn't do are error checking and security checking, and specifying in what directory the files should be saved.
Duplicate question but here's what you need:
Depending if windows or linux, first set to binary mode:
try:
import msvcrt
msvcrt.setmode (0, os.O_BINARY)
msvcrt.setmode (1, os.O_BINARY)
except ImportError:
pass
Then:
form = cgi.FieldStorage()
name = form[filename]
period = name.split('.') #You need the quotes around the period
if period[1]=='jpeg' or period[1] == 'jpg' or period[1] =='png':
if upload.filename:
name = os.path.basename(upload.filename)
out = open(YOUR_FILEPATH_HERE + name, 'wb', 1000)
message = "The file '" + name + "' was uploaded successfully"
while True:
packet = upload.file.read(1000)
if not packet:
break
out.write(packet)
out.close()
else:
print 'Error'
Some sources:
How to use Python/CGI for file uploading
http://code.activestate.com/recipes/273844-minimal-http-upload-cgi/

Assign album artwork with mutagen mac vs. pc

I am trying to assign a picture to a song, and I have some code that works on mac, but not on PC.
from mutagen.easyid3 import EasyID3
from mutagen.id3 import ID3, APIC, error
from mutagen.mp3 import MP3
def image_assigner(self):
song = MP3(self.file, ID3=ID3)
# add ID3 tag if it doesn't exist
try:
song.add_tags()
except error:
print "we got an image error"
pass
song.tags.add(
APIC(
encoding=3,
mime='image/jpeg',
type=2,
desc=u'Cover',
data=open('example.JPG', 'rb').read()
)
)
song.save()
So on Mac, this code works, but when I try it on my PC, it won't. Any help would be appreciated. Thanks!
Edit
So, after doing some more research, I figured out that this code does save the album artwork to the mp3 file on Mac as well as Windows, but it saves it in ID3v2.4, which Mac can read, but Windows can't read, so it appeared like it didn't save it on Windows. It appears that using the v1=2 option in the mutagen save function should save the tags in ID3v1 (see the Oct. 4th post on this page). It seems to work if I update the tags for album, artist, title, etc., but when I do it for album artwork, it still doesn't show up in Windows explorer. Does anybody have experience in this area and could shed some light on this? Thanks.
Yeah unfortunately Windows doesn't support that version. Instead of just saving it in ID3v1 try saving it in ID3v3 and ID3v1. I use this in my programs and it works great in Windows 8 and OSX.
from mutagen.mp3 import MP3
from mutagen.id3 import ID3, APIC, error, TRCK, TIT2, TPE1, TALB, TDRC, TCON
audio = MP3([PATH_TO_FILE], ID3=ID3)
audio.tags.delete([PATH_TO_FILE], delete_v1=True, delete_v2=True)
audio.tags.add(
APIC(
encoding=3,
mime='image/jpeg',
type=3,
desc=u'Cover',
data=open([PATH_TO_COVER_IMAGE], 'rb').read()
)
)
audio.save([PATH_TO_FILE], v2_version=3, v1=2)

Categories