smarnach / pyexiftool

a Python library to communicate with an instance of Phil Harvey's excellent ExifTool command-line application.
Other
270 stars 111 forks source link

TypeError: a bytes-like object is required, not 'str' #29

Closed CyberDefend3r closed 4 years ago

CyberDefend3r commented 4 years ago

I am trying to do something super simple. Well that I thought was simple. I spent like 20 min writing the code and about 2 hours trying to figure out why it doesn't work...

I keep getting a typeError from subprocess called from the exiftool start(). I am sure I am doing something wrong but I cant figure it out. I thought i would post here on the very slim chance this is a bug and not my fault.

"""
Used to take pictures seperated into folders based on date like year/month/photo.jpg, and 
change the create date of photo to the year and month from the folder names.

The script assumes that exiftool.exe and script are in the same directory. year folders are 
in same dir as well.
"""

import os
from glob import glob
import exiftool

class collection:
    def __init__(self):
        self.dateFile = []
        self.fileList = {}
        self.paths = glob('**/**')
        self.curDir = os.getcwd()
        self.exifTool = os.path.join(self.curDir.encode(), 'exiftool.exe'.encode())

    def setVariables(self):
        for path in self.paths:
            globPath = os.path.join(path, '*.*')
            picturPath = glob(globPath)
            pictures = []
            for pic in picturPath:
                pictures.append(os.path.join(self.curDir, pic))
            date = str.replace(path, '\\', ':') + ':01 00:01:01'
            self.fileList = {'date':date, 'pictures':pictures}
            self.dateFile.append(self.fileList)

class changeDate:
    def __init__(self, collection):
        self.dateFile = collection.dateFile
        self.exifTool = exiftool.fsencode(collection.exifTool)

    def buildEXIFcommand(self):
        for filelist in self.dateFile:
            date = filelist['date']
            for picfile in filelist['pictures']:
                modDate = '"' + '-FileCreateDate=' + date + '"'
                modDate.encode()
                picfile = exiftool.fsencode(picfile)
                self.exifChange(modDate, picfile)

    def exifChange(self, modDate, picfile):
        with exiftool.ExifTool(self.exifTool) as et:
            et.execute(modDate, picfile)

if __name__ == "__main__":
    Collection = collection()
    Collection.setVariables()
    ChangeDate = changeDate(Collection)
    ChangeDate.buildEXIFcommand()


Traceback (most recent call last):
  File "c:\Users\trevo\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\ptvsd_launcher.py", line 43, in <module>
    main(ptvsdArgs)
  File "c:\Users\trevo\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\old_ptvsd\ptvsd\__main__.py", line 432, in main
    run()
  File "c:\Users\trevo\.vscode\extensions\ms-python.python-2019.10.44104\pythonFiles\lib\python\old_ptvsd\ptvsd\__main__.py", line 316, in run_file
    runpy.run_path(target, run_name='__main__')
  File "C:\Users\trevo\AppData\Local\Programs\Python\Python37-32\lib\runpy.py", line 263, in run_path
    pkg_name=pkg_name, script_name=fname)
  File "C:\Users\trevo\AppData\Local\Programs\Python\Python37-32\lib\runpy.py", line 96, in _run_module_code
    mod_name, mod_spec, pkg_name, script_name)
  File "C:\Users\trevo\AppData\Local\Programs\Python\Python37-32\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "c:\Users\trevo\OneDrive\Desktop\pics\datchange.py", line 46, in <module>
    ChangeDate.buildEXIFcommand()
  File "c:\Users\trevo\OneDrive\Desktop\pics\datchange.py", line 36, in buildEXIFcommand
    self.exifChange(modDate, picfile)
  File "c:\Users\trevo\OneDrive\Desktop\pics\datchange.py", line 39, in exifChange
    with exiftool.ExifTool(self.exifTool) as et:
  File "C:\Users\trevo\AppData\Local\Programs\Python\Python37-32\lib\site-packages\exiftool.py", line 192, in __enter__
    self.start()
  File "C:\Users\trevo\AppData\Local\Programs\Python\Python37-32\lib\site-packages\exiftool.py", line 175, in start
    stderr=devnull)
  File "C:\Users\trevo\AppData\Local\Programs\Python\Python37-32\lib\subprocess.py", line 775, in __init__
    restore_signals, start_new_session)
  File "C:\Users\trevo\AppData\Local\Programs\Python\Python37-32\lib\subprocess.py", line 1119, in _execute_child
    args = list2cmdline(args)
  File "C:\Users\trevo\AppData\Local\Programs\Python\Python37-32\lib\subprocess.py", line 530, in list2cmdline
    needquote = (" " in arg) or ("\t" in arg) or not arg
TypeError: a bytes-like object is required, not 'str'
jwag956 commented 4 years ago

Looks like the problem is that 'modData.encode()' RETURNS an encoded string - it doesn't modify modDate - so you are still passing in a str.

CyberDefend3r commented 4 years ago

Looks like the problem is that 'modData.encode()' RETURNS an encoded string - it doesn't modify modDate - so you are still passing in a str.

Thank you for pointing this out. Seems that no matter what i do to it it stays a string... Do you have any suggestions?

jwag956 commented 4 years ago

modDataBytes = modData.encode() should work fine

On Mon, Nov 18, 2019 at 9:05 AM trevormiller6 notifications@github.com wrote:

Looks like the problem is that 'modData.encode()' RETURNS an encoded string - it doesn't modify modDate - so you are still passing in a str.

Thank you for pointing this out. Seems that no matter what i do to it it stays a string... Do you have any suggestions?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/smarnach/pyexiftool/issues/29?email_source=notifications&email_token=AAHU2T66ONSVUXEHMUOZPW3QULDPDA5CNFSM4JOOK3AKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEELFMAA#issuecomment-555111936, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAHU2T2JSFE5QH4VKKRD2X3QULDPDANCNFSM4JOOK3AA .

--

Chris Wagner

The land embracing Point Lobos was bought for inclusion in the State Park System (a) because the very peculiar physical characteristics of the locality have enabled many people to obtain here personal satisfactions of peculiar kinds which they have valued very highly, and of which the most highly valued are unobtainable elsewhere in like degree if at all, ...

     Point Lobos Reserve Master Plan Report - Olmsted Brothers,

November, 1935

CyberDefend3r commented 4 years ago

modDataBytes = modData.encode() should work fine

Same issue.

    def buildEXIFcommand(self):
        for filelist in self.dateFile:
            date = filelist['date']
            for picfile in filelist['pictures']:
                modDate = '"' + '-FileCreateDate=' + date + '"'
                modDateBytes = modDate.encode()
                picfile = exiftool.fsencode(picfile)
                self.exifChange(modDateBytes, picfile)

    def exifChange(self, modDateBytes, picfile):
        with exiftool.ExifTool(self.exifTool) as et:
            et.execute(modDateBytes, picfile)
jwag956 commented 4 years ago

Goodpoint - looking closer - it is failing at the call to 'start' (which is run as part of the 'with' context manager) - so not even looking at your parameters yet.

Looking at the error in subprocess - (which is windows specific alas) - it appears that it (error message not withstanding) it wants a string - not bytes. And as I said before - this is on the start - not your 'execute' command - so that means it must be an issue with your executable. So I would try creating your path WITHOUT .encode() e.g. :

os.path.join(self.curDir, 'exiftool.exe')

lets see what that brings.

CyberDefend3r commented 4 years ago

Goodpoint - looking closer - it is failing at the call to 'start' (which is run as part of the 'with' context manager) - so not even looking at your parameters yet.

Looking at the error in subprocess - (which is windows specific alas) - it appears that it (error message not withstanding) it wants a string - not bytes. And as I said before - this is on the start - not your 'execute' command - so that means it must be an issue with your executable. So I would try creating your path WITHOUT .encode() e.g. :

os.path.join(self.curDir, 'exiftool.exe')

lets see what that brings.

I get the same traceback

I can confirm all three variables passed to exiftool are bytes objects when printed:

b'C:\\Users\\trevo\\OneDrive\\Desktop\\pics\\exiftool.exe'
b'-FileCreateDate=2018:06:01 00:01:01'
b'C:\\Users\\trevo\\OneDrive\\Desktop\\pics\\2018\\06\\MVIMG_20190719_185858.jpg'

Thanks for the help!

jwag956 commented 4 years ago

That is my point. Your path to exiftool executable should NOT BE BYTES.

When you use the 'with' stmt - it ends up calling:

self._process = subprocess.Popen( [self.executable, "-stay_open", "True", "-@", "-", "-common_args", "-G", "-n"],

Where self.executable is what you called exifTool's constructor with. Popen takes strings.

However - when you call 'execute(...)' - it is writing to a pipe - and that required bytes - so your date and filename need to be bytes.

CyberDefend3r commented 4 years ago

That is my point. Your path to exiftool executable should NOT BE BYTES.

When you use the 'with' stmt - it ends up calling:

self._process = subprocess.Popen( [self.executable, "-stay_open", "True", "-@", "-", "-common_args", "-G", "-n"],

Where self.executable is what you called exifTool's constructor with. Popen takes strings.

However - when you call 'execute(...)' - it is writing to a pipe - and that required bytes - so your date and filename need to be bytes.

lol you're right i was being an idiot. I was thinking that was being passed to exiftool so I was doing :

self.exifTool = os.path.join(self.curDir.encode(), 'exiftool.exe'.encode())

AND for some reason:

self.exifTool = exiftool.fsencode(collection.exifTool)

So i changed both of those and ended up getting "OSError: [WinError 10038] An operation was attempted on something that is not a socket" I changed the exiftool.py to be from the commit recommended in that issue and all works now!

Thank you for the help! Much appreciated.

CyberDefend3r commented 4 years ago

working code:

"""
Used to take pictures seperated into folders based on date like year/month/photo.jpg, and 
change the create date of photo to the year and month from the folder names.

The script assumes that exiftool.exe and script are in the same directory. year folders are 
in same dir as well.
"""

import os
from glob import glob
import exiftool

class collection:
    def __init__(self):
        self.dateFile = []
        self.fileList = {}
        self.paths = glob('**/**')
        self.curDir = os.getcwd()
        self.exifTool = os.path.join(self.curDir, 'exiftool.exe')
        self.overwriteOG = b'-overwrite_original'

    def setVariables(self):
        for path in self.paths:
            globPath = os.path.join(path, '*.*')
            picturPath = glob(globPath)
            pictures = []
            for pic in picturPath:
                pictures.append(os.path.join(self.curDir, pic))
            date = str.replace(path, '\\', ':') + ':01 00:01:01'
            self.fileList = {'date':date, 'pictures':pictures}
            self.dateFile.append(self.fileList)

class changeDate:
    def __init__(self, collection):
        self.dateFile = collection.dateFile
        self.exifTool = collection.exifTool
        self.overwriteOG = collection.overwriteOG

    def buildEXIFcommand(self):
        for filelist in self.dateFile:
            date = filelist['date']
            for picfile in filelist['pictures']:
                modAllDate = b'-AllDates=' + date.encode()
                modCreateDate = b'-FileCreateDate=' + date.encode()
                picfile = exiftool.fsencode(picfile)
                self.exifChange(modAllDate, modCreateDate, picfile)

    def exifChange(self, modAllDate, modCreateDate, picfile):
        with exiftool.ExifTool(self.exifTool) as et:
            et.execute(self.overwriteOG, modAllDate, modCreateDate, picfile)

if __name__ == "__main__":
    Collection = collection()
    print('Collecting Files To change')
    Collection.setVariables()
    ChangeDate = changeDate(Collection)
    print('Changing date on collected files')
    ChangeDate.buildEXIFcommand()
    print('Completed')
amitabharora commented 3 years ago

Old thread but this helped me understand how to pass arguments to execute. Can be confusing. Thanks.