AttributeError: 'str' object has no attribute 'write', youtube-dl kivy

Viewed 1211

I am currently working on a small project which requies youtube-dl to work kivy environment. I have python 3.8.3, Kivy 1.11.1, youtube-dl 2020.5.8. Other than this issue kivy is working fine with python3.8.

I get this error AttributeError: 'str' object has no attribute 'write' while try to fetch information about a youtube video using youtube-dl

It does works with a few link and does not work with many others.

This error has already is already present on youtube-dl's github page [[here]][1], but I could not find any solution.

These are the two following cases with a reproducible example.

Case 1

import youtube_dl
from kivy.app import App
from kivy.uix.button import Button


class MusicApp(App):
    def build(self):
        return Button(text='Example')


ydl_opts = {
    'outtmpl': '%(title)s.%(ext)s',
    'audio-format': 'bestaudio/best',
    'postprocessors': [{
        'key': 'FFmpegExtractAudio',
        'preferredcodec': 'mp3',
        'preferredquality': '192',
    }],
}
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
    ydl.download(['https://www.youtube.com/watch?v=BaW_jenozKc'])

Output

[INFO   ] [Logger      ] Record log in /home/adarsh/.kivy/logs/kivy_20-06-23_1.txt
[INFO   ] [Kivy        ] v1.11.1
[INFO   ] [Kivy        ] Installed at "/usr/local/lib/python3.8/dist-packages/kivy/__init__.py"
[INFO   ] [Python      ] v3.8.2 (default, Apr 27 2020, 15:53:34) 
[GCC 9.3.0]
[INFO   ] [Python      ] Interpreter at "/usr/bin/python3"
[INFO   ] [Factory     ] 184 symbols loaded
[INFO   ] [Image       ] Providers: img_tex, img_dds, img_sdl2, img_pil, img_gif (img_ffpyplayer ignored)
[INFO   ] [Text        ] Provider: sdl2(['text_pango'] ignored)
[youtube] BaW_jenozKc: Downloading webpage
[download] Resuming download at byte 215799
[download] Destination: youtube-dl test video ''_ä↭.f137.mp4
[download] 100% of 2.11MiB in 00:07.90KiB/s ETA 00:00
[download] Destination: youtube-dl test video ''_ä↭.f140.m4a
[download] 100% of 154.06KiB in 00:00.07KiB/s ETA 00:00
[ffmpeg] Merging formats into "youtube-dl test video ''_ä↭.mp4"
Deleting original file youtube-dl test video ''_ä↭.f137.mp4 (pass -k to keep)
Deleting original file youtube-dl test video ''_ä↭.f140.m4a (pass -k to keep)
[ffmpeg] Destination: youtube-dl test video ''_ä↭.mp3
Deleting original file youtube-dl test video ''_ä↭.mp4 (pass -k to keep)

Case 2

import youtube_dl
from kivy.app import App
from kivy.uix.button import Button


class MusicApp(App):
    def build(self):
        return Button(text='Example')


ydl_opts = {
    'outtmpl': '%(title)s.%(ext)s',
    'audio-format': 'bestaudio/best',
    'postprocessors': [{
        'key': 'FFmpegExtractAudio',
        'preferredcodec': 'mp3',
        'preferredquality': '192',
    }],
}
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
    ydl.download(['https://www.youtube.com/watch?v=nTj4wVJAbNg'])

Output

[INFO   ] [Logger      ] Record log in /home/adarsh/.kivy/logs/kivy_20-06-23_2.txt
[INFO   ] [Kivy        ] v1.11.1
[INFO   ] [Kivy        ] Installed at "/usr/local/lib/python3.8/dist-packages/kivy/__init__.py"
[INFO   ] [Python      ] v3.8.2 (default, Apr 27 2020, 15:53:34) 
[GCC 9.3.0]
[INFO   ] [Python      ] Interpreter at "/usr/bin/python3"
[INFO   ] [Factory     ] 184 symbols loaded
[INFO   ] [Image       ] Providers: img_tex, img_dds, img_sdl2, img_pil, img_gif (img_ffpyplayer ignored)
[INFO   ] [Text        ] Provider: sdl2(['text_pango'] ignored)
[youtube] nTj4wVJAbNg: Downloading webpage
 Traceback (most recent call last):
   File "temp/tmp.py", line 21, in <module>
     ydl.download(['https://www.youtube.com/watch?v=nTj4wVJAbNg'])
   File "/home/adarsh/.local/lib/python3.8/site-packages/youtube_dl/YoutubeDL.py", line 2018, in download
     res = self.extract_info(
   File "/home/adarsh/.local/lib/python3.8/site-packages/youtube_dl/YoutubeDL.py", line 808, in extract_info
     return self.process_ie_result(ie_result, download, extra_info)
   File "/home/adarsh/.local/lib/python3.8/site-packages/youtube_dl/YoutubeDL.py", line 863, in process_ie_result
     return self.process_video_result(ie_result, download=download)
   File "/home/adarsh/.local/lib/python3.8/site-packages/youtube_dl/YoutubeDL.py", line 1644, in process_video_result
     self.process_info(new_info)
   File "/home/adarsh/.local/lib/python3.8/site-packages/youtube_dl/YoutubeDL.py", line 1902, in process_info
     self.report_warning(
   File "/home/adarsh/.local/lib/python3.8/site-packages/youtube_dl/YoutubeDL.py", line 613, in report_warning
     self.to_stderr(warning_message)
   File "/home/adarsh/.local/lib/python3.8/site-packages/youtube_dl/YoutubeDL.py", line 527, in to_stderr
     self._write_string(output, self._err_file)
   File "/home/adarsh/.local/lib/python3.8/site-packages/youtube_dl/YoutubeDL.py", line 506, in _write_string
     write_string(s, out=out, encoding=self.params.get('encoding'))
   File "/home/adarsh/.local/lib/python3.8/site-packages/youtube_dl/utils.py", line 3174, in write_string
     out.buffer.write(byt)
 AttributeError: 'str' object has no attribute 'write'

Please suggest even if there are some workarounds.

EDIT : I passed a logger to youtube-dl taken from their github page

class MyLogger(object):
    def debug(self, msg):
        print("DEBUG " + msg)
        pass

    def warning(self, msg):
        print("WARNING " + msg)
        pass

    def error(self, msg):
        print("ERROR " + msg)

I needed to write my own logger at later point to get the progress of download. So I am going with this solution. [1]: https://github.com/ytdl-org/youtube-dl/issues/22109

3 Answers

I am working on a similar project and got the same error.

I catched Exceptions and print the traceback

import traceback

... 

  try:
     with youtube_dl.YoutubeDL(self.ydl_opts) as ydl:
        ydl.download([self.url])
  except Exception as inst:
     print(inst)
     tb = traceback.format_exc()
     print(tb)
     pass

and I got this :

'str' object has no attribute 'write'
Traceback (most recent call last):
  File "/home/odrevet/.local/lib/python3.6/site-packages/youtube_dl/YoutubeDL.py", line 797, in extract_info
    ie_result = ie.extract(url)
  File "/home/odrevet/.local/lib/python3.6/site-packages/youtube_dl/extractor/common.py", line 530, in extract
    ie_result = self._real_extract(url)
  File "/home/odrevet/.local/lib/python3.6/site-packages/youtube_dl/extractor/generic.py", line 2277, in _real_extract
    % (url, url), expected=True)
youtube_dl.utils.ExtractorError: 'qsd' is not a valid URL. Set --default-search "ytsearch" (or run  youtube-dl "ytsearch:qsd" ) to search YouTube

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "src/main.py", line 81, in run
    download_retcode = ydl.download([self.url])
  File "/home/odrevet/.local/lib/python3.6/site-packages/youtube_dl/YoutubeDL.py", line 2019, in download
    url, force_generic_extractor=self.params.get('force_generic_extractor', False))
  File "/home/odrevet/.local/lib/python3.6/site-packages/youtube_dl/YoutubeDL.py", line 820, in extract_info
    self.report_error(compat_str(e), e.format_traceback())
  File "/home/odrevet/.local/lib/python3.6/site-packages/youtube_dl/YoutubeDL.py", line 625, in report_error
    self.trouble(error_message, tb)
  File "/home/odrevet/.local/lib/python3.6/site-packages/youtube_dl/YoutubeDL.py", line 578, in trouble
    self.to_stderr(message)
  File "/home/odrevet/.local/lib/python3.6/site-packages/youtube_dl/YoutubeDL.py", line 527, in to_stderr
    self._write_string(output, self._err_file)
  File "/home/odrevet/.local/lib/python3.6/site-packages/youtube_dl/YoutubeDL.py", line 506, in _write_string
    write_string(s, out=out, encoding=self.params.get('encoding'))
  File "/home/odrevet/.local/lib/python3.6/site-packages/youtube_dl/utils.py", line 3180, in write_string
    out.buffer.write(byt)
AttributeError: 'str' object has no attribute 'write'

By looking to the function's name in the stack, we can see that our error occure when ytdl try to report another error (in this case : "youtube_dl.utils.ExtractorError: 'qsd' is not a valid URL", because I entered 'qsd' in my program instead of a valid url).

By looking in YoutubeDl.py in the function to_stderr we can see that out is supposed to be sys.stderr, and in utils.py in the function where the error occure :

def write_string(s, out=None, encoding=None):
    print(type(out))    # I added this line
    if out is None:
        out = sys.stderr
    assert type(s) == compat_str

    if sys.platform == 'win32' and encoding is None and hasattr(out, 'fileno'):
        if _windows_write_string(s, out):
            return

    if ('b' in getattr(out, 'mode', '')
            or sys.version_info[0] < 3):  # Python 2 lies about mode of sys.stderr
        byt = s.encode(encoding or preferredencoding(), 'ignore')
        out.write(byt)
    elif hasattr(out, 'buffer'):
        enc = encoding or getattr(out, 'encoding', None) or preferredencoding()
        byt = s.encode(enc, 'ignore')
        out.buffer.write(byt)    # Error on line 3180
    else:
        out.write(s)
  out.flush()

stderr has a buffer attribute according to the hasattr elif check, the problem is how Kivy is handling the error stream. According to the print I added in write_string, stderr is redefined as a <class 'kivy.logger.LogFile'> which do have a buffer attribute like the Stream it's supposed to be, but this attribute is a simple String.

See https://kivy.org/doc/stable/_modules/kivy/logger.html

class LogFile(object):

    def __init__(self, channel, func):
        self.buffer = ''     # <-- a str
        self.func = func
        self.channel = channel
        self.errors = ''

I don't known if it's possible to parameter kivy to use standard streams for stderr but I will check. Meanwhile catch the exception and print backtrace to at least have the original error before the "During handling of the above exception, another exception occurred"

edit: you can redirect stderr to a file

sys.stderr = open('./stderr', 'w')

I had the same problem when running my kivy app using youtube_dl on Android to download the audio track of a single video. The download of the same video audio worked ok on Windows 10.

I fixed the problem by changing the audio format in the youtube_dl options. Here's the code excerpt:

if os.name == 'posix':
    # on AndroidAndroid, FFmpegExtractAudio not available !
    self.ydlOutTmplFormat = '/%(title)s.mp3'

    self.ydl_opts = {
        #'format': 'bestaudio/best', # for unknown reason, not working on
                                     # Android when used by AudioDownloaderGUI !
        'format': 'worstaudio/worst',# This fixes the error "AttributeError:
                                     # 'str' object has no attribute 'write'"
        'quiet': YOUTUBE_DL_QUIET
    }
else:
    self.ydlOutTmplFormat = '\\%(title)s.%(ext)s'
    
    self.ydl_opts = {
        'format': 'bestaudio/best',
        'postprocessors': [{
            'key': 'FFmpegExtractAudio',
            'preferredcodec': 'mp3',
            'preferredquality': '128',
        }],
        'quiet': YOUTUBE_DL_QUIET
    }

I do not have any explanation why this modification solves the problem. When using youtube_dl on Android in a demo program not using kivy, the problem does not happen. Here's my demo code:

import os, re
import youtube_dl

if os.name == 'posix':
    targetAudioDir = '/storage/emulated/0/Download/Audiobooks/test_youtube_dl'
    ydl_opts = {
        'outtmpl': targetAudioDir + '/%(title)s.mp3',
        'format': 'bestaudio/best',
        #'format': 'worstaudio/worst',
        'quiet': False
    }
else:
    targetAudioDir = 'D:\\Users\\Jean-Pierre\\Downloads\\Audiobooks\\test_youtube_dl'
    ydl_opts = {
        'outtmpl': targetAudioDir + '\\%(title)s.%(ext)s',
        #'format': 'bestaudio/best',
        'format': 'worstaudio/worst',
        'postprocessors': [{
                            'key': 'FFmpegExtractAudio',
                            'preferredcodec': 'mp3',
                            'preferredquality': '96',
                        }],
    'quiet': False
    }

singlevideoUrl = 'https://youtu.be/Eqy6M6qLWGw'

with youtube_dl.YoutubeDL(ydl_opts) as ydl:
    # downloading single video
    meta = ydl.extract_info(singlevideoUrl, download=False)
    videoTitle = meta['title']
    ydl.download([singlevideoUrl]) # not playable by kivy SoundLoader
    print('{} audio track downloaded'.format(videoTitle))

I had the same problem, and I solved it using the os library.

  • First I create a convert.py script for example with the YouTube.dl function.

  • Second, in kivy instead of calling YouTube.dl, I save the link in a .txt file, then I call os.system (python3 convert.py) using the txt with the link to convert and ready.

Related