File indexing completed on 2024-05-05 04:55:40

0001 #!/usr/bin/env python3
0002 # -*- coding: utf-8 -*-
0003 import unittest
0004 import sys
0005 import os
0006 import subprocess
0007 import tempfile
0008 import platform
0009 import json
0010 from kid3testsupport import kid3_cli_path, call_kid3_cli, create_test_file
0011 
0012 
0013 def setUpModule():
0014     # Use an invalid config file to use a default configuration.
0015     os.environ['KID3_CONFIG_FILE'] = ''
0016     # Use English locale.
0017     os.environ['LANGUAGE'] = 'en_US.UTF-8'
0018     os.environ['LANG'] = 'en_US.UTF-8'
0019 
0020 
0021 class CliFunctionsTestCase(unittest.TestCase):
0022     def test_help(self):
0023         full_help = call_kid3_cli('-h')
0024         self.assertIn('Available Commands', full_help)
0025         self.assertRegex(full_help, r'help \[S\]\s+Help')
0026         self.assertRegex(full_help, r'^kid3-cli (\d\.\d\.\d|git\d{8}) \(c\) \d{4} Urs Fleisch')
0027         full_help = call_kid3_cli(['-c', 'help'])
0028         self.assertIn('Available Commands', full_help)
0029         self.assertRegex(full_help, r'help \[S\]\s+Help')
0030         self.assertTrue(full_help.startswith('Parameter\n  P = File path'))
0031         self.assertRegex(full_help, r'playlist\s+Create playlist')
0032         self.assertRegex(call_kid3_cli(['-c', 'help paste']), r'paste \[T\]\s+Paste')
0033 
0034     def test_invalid(self):
0035         p = subprocess.Popen([kid3_cli_path(), '-c', 'invalid'],
0036                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)
0037         stdout, stderr = p.communicate()
0038         self.assertEqual(stdout, b'')
0039         self.assertIn(b"Unknown command 'invalid', -h for help.", stderr)
0040         self.assertEqual(p.returncode, 1)
0041 
0042     def test_timeout(self):
0043         self.assertEqual(call_kid3_cli(
0044             ['-c', 'timeout', '-c', 'timeout 5000', '-c', 'timeout',
0045              '-c', 'timeout off', '-c', 'timeout',
0046              '-c', 'timeout default', '-c', 'timeout']),
0047             'Timeout: default\n'
0048             'Timeout: 5000 ms\n'
0049             'Timeout: 5000 ms\n'
0050             'Timeout: off\n'
0051             'Timeout: off\n'
0052             'Timeout: default\n'
0053             'Timeout: default\n')
0054 
0055     def test_exit(self):
0056         self.assertEqual(call_kid3_cli(['-c', 'exit']), '')
0057 
0058     def test_ls(self):
0059         with tempfile.TemporaryDirectory() as tmpdir:
0060             self.assertEqual(call_kid3_cli(['-c', 'ls', tmpdir]), '')
0061             create_test_file(os.path.join(tmpdir, 'test.mp3'))
0062             self.assertEqual(call_kid3_cli(['-c', 'ls', tmpdir]), '  --- test.mp3\n')
0063 
0064     def test_id3v1(self):
0065         with tempfile.TemporaryDirectory() as tmpdir:
0066             mp3path = os.path.join(tmpdir, 'test.mp3')
0067             create_test_file(mp3path)
0068             with open(mp3path, 'rb') as mp3fh:
0069                 empty_mp3_bytes = mp3fh.read()
0070             self.assertEqual(call_kid3_cli(
0071                 ['-c', 'get title 1',
0072                  '-c', 'set title "A Title" 1',
0073                  '-c', 'get title 1',
0074                  '-c', 'get all 1',
0075                  '-c', 'save', mp3path]),
0076                 'A Title\n'
0077                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0078                 '  Name: test.mp3\n'
0079                 'Tag 1: ID3v1.1\n'
0080                 '* Title  A Title\n')
0081             self.assertEqual(call_kid3_cli(
0082                 ['-c', 'set artist "An Artist" 1',
0083                  '-c', 'set album "An Album" 1',
0084                  '-c', 'set track 5 1',
0085                  '-c', 'set genre Metal 1',
0086                  '-c', 'set date 2016 1',
0087                  '-c', 'set comment "A Comment" 1',
0088                  '-c', 'get all 1', mp3path]),
0089                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0090                 '  Name: test.mp3\nTag 1: ID3v1.1\n'
0091                 '  Title         A Title\n'
0092                 '* Artist        An Artist\n'
0093                 '* Album         An Album\n'
0094                 '* Comment       A Comment\n'
0095                 '* Date          2016\n'
0096                 '* Track Number  5\n'
0097                 '* Genre         Metal\n')
0098             with open(mp3path, 'rb') as mp3fh:
0099                 ba = mp3fh.read()
0100                 self.assertEqual(
0101                     ba[-128:],
0102                     b'TAGA '
0103                     b'Title\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
0104                     b'An Artist\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
0105                     b'An Album\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
0106                     b'2016'
0107                     b'A Comment\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\t')
0108             self.assertEqual(call_kid3_cli(
0109                 ['-c', 'copy 1',
0110                  '-c', 'remove 1',
0111                  '-c', 'get all 1',
0112                  '-c', 'paste 1',
0113                  '-c', 'get all 1',
0114                  '-c', 'set track 6 1',
0115                  '-c', 'get all 1',
0116                  '-c', 'revert',
0117                  '-c', 'get all 1',
0118                  '-c', 'remove 1', mp3path]),
0119                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0120                 '  Name: test.mp3\n'
0121                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0122                 '  Name: test.mp3\n'
0123                 'Tag 1: ID3v1.1\n'
0124                 '* Title         A Title\n'
0125                 '* Artist        An Artist\n'
0126                 '* Album         An Album\n'
0127                 '* Comment       A Comment\n'
0128                 '* Date          2016\n'
0129                 '* Track Number  5\n'
0130                 '* Genre         Metal\n'
0131                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0132                 '  Name: test.mp3\nTag 1: ID3v1.1\n'
0133                 '* Title         A Title\n'
0134                 '* Artist        An Artist\n'
0135                 '* Album         An Album\n'
0136                 '* Comment       A Comment\n'
0137                 '* Date          2016\n'
0138                 '* Track Number  6\n'
0139                 '* Genre         Metal\n'
0140                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0141                 '  Name: test.mp3\n'
0142                 'Tag 1: ID3v1.1\n'
0143                 '  Title         A Title\n'
0144                 '  Artist        An Artist\n'
0145                 '  Album         An Album\n'
0146                 '  Comment       A Comment\n'
0147                 '  Date          2016\n'
0148                 '  Track Number  5\n'
0149                 '  Genre         Metal\n')
0150             with open(mp3path, 'rb') as mp3fh:
0151                 ba = mp3fh.read()
0152                 self.assertEqual(ba, empty_mp3_bytes)
0153 
0154     def test_id3v2(self):
0155         with tempfile.TemporaryDirectory() as tmpdir:
0156             mp3path = os.path.join(tmpdir, 'test.mp3')
0157             jpgpath = os.path.join(tmpdir, 'test.jpg')
0158             picpath = os.path.join(tmpdir, 'folder.jpg')
0159             create_test_file(mp3path)
0160             create_test_file(jpgpath)
0161             with open(mp3path, 'rb') as mp3fh:
0162                 empty_mp3_bytes = mp3fh.read()
0163             with open(jpgpath, 'rb') as jpgfh:
0164                 jpg_bytes = jpgfh.read()
0165             self.assertEqual(call_kid3_cli(
0166                 ['-c', 'get title 2',
0167                  '-c', 'get all 2', mp3path]),
0168                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0169                 '  Name: test.mp3\n')
0170             self.assertEqual(call_kid3_cli(
0171                 ['-c', 'set artist "An Artist" 2',
0172                  '-c', 'set album "An Album" 2',
0173                  '-c', 'set track 3 2',
0174                  '-c', 'set genre "Power Metal" 2',
0175                  '-c', 'set date 2016 2',
0176                  '-c', 'set comment "A Comment" 2',
0177                  '-c', 'set Lyricist "A Lyricist" 2',
0178                  '-c', 'set TDLY 100 2',
0179                  '-c', 'set picture:"%s" "A Description" 2' % jpgpath,
0180                  '-c', 'set comment "A Comment" 2',
0181                  '-c', 'get all 2', mp3path]),
0182                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0183                 '  Name: test.mp3\n'
0184                 'Tag 2: ID3v2.3.0\n'
0185                 '* Artist                  An Artist\n'
0186                 '* Album                   An Album\n'
0187                 '* Comment                 A Comment\n'
0188                 '* Date                    2016\n'
0189                 '* Track Number            3\n'
0190                 '* Genre                   Power Metal\n'
0191                 '* Lyricist                A Lyricist\n'
0192                 '* Picture: Cover (front)  A Description\n'
0193                 '* Playlist Delay          100\n')
0194             self.assertEqual(call_kid3_cli(
0195                 ['-c', 'copy 2',
0196                  '-c', 'remove 2',
0197                  '-c', 'get all 2',
0198                  '-c', 'paste 2',
0199                  '-c', 'get all 2',
0200                  '-c', 'set track 6 2',
0201                  '-c', 'syncto 1',
0202                  '-c', 'get all',
0203                  '-c', 'revert',
0204                  '-c', 'get all',
0205                  '-c', 'to24',
0206                  '-c', 'get all 2',
0207                  '-c', 'to23',
0208                  '-c', 'get picture:"%s" "A Description" 2' % picpath,
0209                  '-c', 'get all 2',
0210                  '-c', 'remove 2', mp3path]),
0211                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0212                 '  Name: test.mp3\n'
0213                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0214                 '  Name: test.mp3\n'
0215                 'Tag 2: ID3v2.3.0\n'
0216                 '* Artist                  An Artist\n'
0217                 '* Album                   An Album\n'
0218                 '* Comment                 A Comment\n'
0219                 '* Date                    2016\n'
0220                 '* Track Number            3\n'
0221                 '* Genre                   Power Metal\n'
0222                 '* Lyricist                A Lyricist\n'
0223                 '* Picture: Cover (front)  A Description\n'
0224                 '* Playlist Delay          100\n'
0225                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0226                 '  Name: test.mp3\n'
0227                 'Tag 1: ID3v1.1\n'
0228                 '* Artist        An Artist\n'
0229                 '* Album         An Album\n'
0230                 '* Comment       A Comment\n'
0231                 '* Date          2016\n'
0232                 '* Track Number  6\n'
0233                 'Tag 2: ID3v2.3.0\n'
0234                 '* Artist                  An Artist\n'
0235                 '* Album                   An Album\n'
0236                 '* Comment                 A Comment\n'
0237                 '* Date                    2016\n'
0238                 '* Track Number            6\n'
0239                 '* Genre                   Power Metal\n'
0240                 '* Lyricist                A Lyricist\n'
0241                 '* Picture: Cover (front)  A Description\n'
0242                 '* Playlist Delay          100\n'
0243                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0244                 '  Name: test.mp3\n'
0245                 'Tag 2: ID3v2.3.0\n'
0246                 '  Artist                  An Artist\n'
0247                 '  Album                   An Album\n'
0248                 '  Comment                 A Comment\n'
0249                 '  Date                    2016\n'
0250                 '  Track Number            3\n'
0251                 '  Genre                   Power Metal\n'
0252                 '  Lyricist                A Lyricist\n'
0253                 '  Picture: Cover (front)  A Description\n'
0254                 '  Playlist Delay          100\n'
0255                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0256                 '  Name: test.mp3\n'
0257                 'Tag 2: ID3v2.4.0\n'
0258                 '  Artist                  An Artist\n'
0259                 '  Album                   An Album\n'
0260                 '  Comment                 A Comment\n'
0261                 '  Date                    2016\n'
0262                 '  Track Number            3\n'
0263                 '  Genre                   Power Metal\n'
0264                 '  Lyricist                A Lyricist\n'
0265                 '  Picture: Cover (front)  A Description\n'
0266                 '  Playlist Delay          100\n'
0267                 'A Description\n'
0268                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0269                 '  Name: test.mp3\n'
0270                 'Tag 2: ID3v2.3.0\n'
0271                 '  Artist                  An Artist\n'
0272                 '  Album                   An Album\n'
0273                 '  Comment                 A Comment\n'
0274                 '  Date                    2016\n'
0275                 '  Track Number            3\n'
0276                 '  Genre                   Power Metal\n'
0277                 '  Lyricist                A Lyricist\n'
0278                 '  Picture: Cover (front)  A Description\n'
0279                 '  Playlist Delay          100\n')
0280             with open(mp3path, 'rb') as mp3fh:
0281                 ba = mp3fh.read()
0282                 self.assertEqual(ba, empty_mp3_bytes)
0283             with open(picpath, 'rb') as jpgfh:
0284                 ba = jpgfh.read()
0285                 self.assertEqual(ba, jpg_bytes)
0286             self.assertEqual(call_kid3_cli(
0287                 ['-c', 'set POPM 5',
0288                  '-c', 'get all 2',
0289                  '-c', 'get POPM.Email',
0290                  '-c', 'get POPM.Rating',
0291                  '-c', 'get POPM.Counter',
0292                  '-c', 'set POPM.Email ufleisch@users.sourceforge.net',
0293                  '-c', 'set POPM.Rating 4',
0294                  '-c', 'set POPM.Counter 3',
0295                  '-c', 'get all 2',
0296                  '-c', 'get POPM.Email',
0297                  '-c', 'get POPM.Rating',
0298                  '-c', 'get POPM.Counter',
0299                  '-c', 'get POPM', mp3path]),
0300                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0301                 '  Name: test.mp3\n'
0302                 'Tag 2: ID3v2.3.0\n'
0303                 '* Rating  5\n'
0304                 '\n'
0305                 '5\n'
0306                 '0\n'
0307                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0308                 '  Name: test.mp3\n'
0309                 'Tag 2: ID3v2.3.0\n'
0310                 '* Rating  4\n'
0311                 'ufleisch@users.sourceforge.net\n'
0312                 '4\n'
0313                 '3\n'
0314                 '4\n')
0315 
0316     def test_flac(self):
0317         with tempfile.TemporaryDirectory() as tmpdir:
0318             flacpath = os.path.join(tmpdir, 'test.flac')
0319             jpgpath = os.path.join(tmpdir, 'test.jpg')
0320             picpath = os.path.join(tmpdir, 'folder.jpg')
0321             create_test_file(flacpath)
0322             create_test_file(jpgpath)
0323             with open(jpgpath, 'rb') as jpgfh:
0324                 jpg_bytes = jpgfh.read()
0325             self.assertRegex(call_kid3_cli(
0326                 ['-c', 'get title 2',
0327                  '-c', 'get all 2', flacpath]),
0328                 'File: FLAC [^\\n]+ kbps 44100 Hz 1 Channels\n'
0329                 '  Name: test\\.flac\n')
0330             self.assertRegex(call_kid3_cli(
0331                 ['-c', 'set artist "A first artist"',
0332                  '-c', 'set artist[1] "A second artist"',
0333                  '-c', 'set album "Album Name"',
0334                  '-c', 'set track 4',
0335                  '-c', 'set genre "Heavy Metal"',
0336                  '-c', 'set date 2017',
0337                  '-c', 'set comment[0] "Comment 1"',
0338                  '-c', 'set comment[1] "Comment 2"',
0339                  '-c', 'set comment[2] "Comment 3"',
0340                  '-c', 'set Lyricist "A Lyricist" 2',
0341                  '-c', 'set tracktotal 12',
0342                  '-c', 'set "disc number" 1',
0343                  '-c', 'set DISCTOTAL 2',
0344                  '-c', 'set picture:"%s" "Picture Description"' % jpgpath,
0345                  '-c', 'get comment[1]',
0346                  '-c', 'get comment',
0347                  '-c', 'get', flacpath]),
0348                  'Comment 2\n'
0349                  'Comment 1\n'
0350                  'File: FLAC [^\\n]+ kbps 44100 Hz 1 Channels\n'
0351                  '  Name: test\\.flac\n'
0352                  'Tag 2: Vorbis\n'
0353                  '\\* Artist                  A first artist\n'
0354                  '\\* Artist                  A second artist\n'
0355                  '\\* Album                   Album Name\n'
0356                  '\\* Comment                 Comment 1\n'
0357                  '\\* Comment                 Comment 2\n'
0358                  '\\* Comment                 Comment 3\n'
0359                  '\\* Date                    2017\n'
0360                  '\\* Track Number            4\n'
0361                  '\\* Genre                   Heavy Metal\n'
0362                  '\\* Disc Number             1\n'
0363                  '\\* Lyricist                A Lyricist\n'
0364                  '\\* Picture: Cover \\(front\\)  Picture Description\n'
0365                  '\\* Total Discs             2\n'
0366                  '\\* Total Tracks            12\n')
0367             self.assertRegex(call_kid3_cli(
0368                 ['-c', 'set artist[1] ""',
0369                  '-c', 'set comment[1] "Comment B"',
0370                  '-c', 'set comment[0] "Comment A"',
0371                  '-c', 'set comment[2] "Comment C"',
0372                  '-c', 'set lyricist ""',
0373                  '-c', 'set picture[1]:"%s" "Back Cover"' % jpgpath,
0374                  '-c', 'set picture[1].picturetype 4',
0375                  '-c', 'set picture[0].description "Front Cover"',
0376                  '-c', 'get picture[1]:"%s"' % picpath,
0377                  '-c', 'get',
0378                  '-c', 'remove',
0379                  '-c', 'get', flacpath]),
0380                  'Back Cover\n'
0381                  'File: FLAC [^\\n]+ kbps 44100 Hz 1 Channels\n'
0382                  '  Name: test\\.flac\n'
0383                  'Tag 2: Vorbis\n'
0384                  '\\* Artist                  A first artist\n'
0385                  '  Album                   Album Name\n'
0386                  '\\* Comment                 Comment A\n'
0387                  '\\* Comment                 Comment B\n'
0388                  '\\* Comment                 Comment C\n'
0389                  '  Date                    2017\n'
0390                  '  Track Number            4\n'
0391                  '  Genre                   Heavy Metal\n'
0392                  '  Disc Number             1\n'
0393                  '\\* Picture: Cover \\(front\\)  Front Cover\n'
0394                  '\\* Picture: Cover \\(back\\)   Back Cover\n'
0395                  '  Total Discs             2\n'
0396                  '  Total Tracks            12\n'
0397                  'File: FLAC [^\\n]+ kbps 44100 Hz 1 Channels\n'
0398                  '  Name: test\\.flac\n')
0399             with open(picpath, 'rb') as jpgfh:
0400                 ba = jpgfh.read()
0401                 self.assertEqual(ba, jpg_bytes)
0402 
0403     def test_frame_selection(self):
0404         with tempfile.TemporaryDirectory() as tmpdir:
0405             test1path = os.path.join(tmpdir, 'test1.mp3')
0406             test2path = os.path.join(tmpdir, 'test2.mp3')
0407             create_test_file(test1path)
0408             create_test_file(test2path)
0409             self.assertEqual(call_kid3_cli(
0410                 ['-c', 'get all 2', test1path]),
0411                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0412                 '  Name: test1.mp3\n')
0413             self.assertEqual(call_kid3_cli(
0414                 ['-c', 'get all 2', test2path]),
0415                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0416                 '  Name: test2.mp3\n')
0417             expected = (
0418                 'Tag 2:\n'
0419                 '  Title        ≠\n'
0420                 '  Artist       An Artist\n'
0421                 '  Album        An Album\n'
0422                 '  Copyright    ≠\n'
0423                 '  Disc Number  ≠\n')
0424             if sys.platform == 'win32':
0425                 expected = expected.replace('≠', '?')
0426             self.assertEqual(call_kid3_cli(
0427                 ['-c', 'select all',
0428                  '-c', 'set artist "An Artist"',
0429                  '-c', 'set album "An Album"',
0430                  '-c', 'select none',
0431                  '-c', 'select first',
0432                  '-c', 'set title "Title 1"',
0433                  '-c', 'set discnumber "1/2"',
0434                  '-c', 'set copyright "2017 Kid3"',
0435                  '-c', 'select next',
0436                  '-c', 'set title "Title 2"',
0437                  '-c', 'save',
0438                  '-c', 'select all',
0439                  '-c', 'get', tmpdir]),
0440                  expected)
0441             self.assertEqual(call_kid3_cli(
0442                 ['-c', 'select first',
0443                  '-c', 'get',
0444                  '-c', 'set "*.selected" 0',
0445                  '-c', 'set "album.selected" 1',
0446                  '-c', 'remove',
0447                  '-c', 'get',
0448                  '-c', 'revert',
0449                  '-c', 'set "*.selected" 0',
0450                  '-c', 'set "discnumber.selected" 1',
0451                  '-c', 'set "copyright.selected" 1',
0452                  '-c', 'copy',
0453                  '-c', 'select next',
0454                  '-c', 'paste',
0455                  '-c', 'get',
0456                  '-c', 'revert',
0457                  '-c', 'select all',
0458                  '-c', 'set "*.selected" 0',
0459                  '-c', 'set "discnumber.selected" 1',
0460                  '-c', 'paste',
0461                  '-c', 'select first',
0462                  '-c', 'get',
0463                  '-c', 'select next',
0464                  '-c', 'get', tmpdir]),
0465                  'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0466                  '  Name: test1.mp3\n'
0467                  'Tag 2: ID3v2.3.0\n'
0468                  '  Title        Title 1\n'
0469                  '  Artist       An Artist\n'
0470                  '  Album        An Album\n'
0471                  '  Copyright    2017 Kid3\n'
0472                  '  Disc Number  1/2\n'
0473                  'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0474                  '  Name: test1.mp3\n'
0475                  'Tag 2: ID3v2.3.0\n'
0476                  '  Title        Title 1\n'
0477                  '  Artist       An Artist\n'
0478                  '  Copyright    2017 Kid3\n'
0479                  '  Disc Number  1/2\n'
0480                  'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0481                  '  Name: test2.mp3\n'
0482                  'Tag 2: ID3v2.3.0\n'
0483                  '  Title        Title 2\n'
0484                  '  Artist       An Artist\n'
0485                  '  Album        An Album\n'
0486                  '* Copyright    2017 Kid3\n'
0487                  '* Disc Number  1/2\n'
0488                  'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0489                  '  Name: test1.mp3\n'
0490                  'Tag 2: ID3v2.3.0\n'
0491                  '  Title        Title 1\n'
0492                  '  Artist       An Artist\n'
0493                  '  Album        An Album\n'
0494                  '  Copyright    2017 Kid3\n'
0495                  '  Disc Number  1/2\n'
0496                  'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0497                  '  Name: test2.mp3\n'
0498                  'Tag 2: ID3v2.3.0\n'
0499                  '  Title        Title 2\n'
0500                  '  Artist       An Artist\n'
0501                  '  Album        An Album\n'
0502                  '* Disc Number  1/2\n')
0503 
0504     def test_riff_info(self):
0505         with tempfile.TemporaryDirectory() as tmpdir:
0506             wavpath = os.path.join(tmpdir, 'test.wav')
0507             create_test_file(wavpath)
0508             with open(wavpath, 'rb') as wavfh:
0509                 empty_wav_bytes = wavfh.read()
0510             self.assertRegex(call_kid3_cli(
0511                 ['-c', 'get title 3',
0512                  '-c', 'get all 3', wavpath]),
0513                 'File: WAV (?:PCM 16 bit )?1411 kbps 44100 Hz 2 Channels\n'
0514                 '  Name: test\\.wav\n')
0515             self.assertRegex(call_kid3_cli(
0516                 ['-c', 'set artist "An Artist" 3',
0517                  '-c', 'set album "An Album" 3',
0518                  '-c', 'set tracknumber 2 3',
0519                  '-c', 'set genre "Heavy Metal" 3',
0520                  '-c', 'set date 2017 3',
0521                  '-c', 'set comment "A Comment" 3',
0522                  '-c', 'set bpm "120" 3',
0523                  '-c', 'set comment "A Comment" 3',
0524                  '-c', 'get all 3', wavpath]),
0525                 'File: WAV (?:PCM 16 bit )?1411 kbps 44100 Hz 2 Channels\n'
0526                 '  Name: test\\.wav\n'
0527                 '(?:Tag 3: RIFF INFO\n'
0528                 '\\* Artist        An Artist\n'
0529                 '\\* Album         An Album\n'
0530                 '\\* Comment       A Comment\n'
0531                 '\\* Date          2017\n'
0532                 '\\* Track Number  2\n'
0533                 '\\* Genre         Heavy Metal\n'
0534                 '\\* BPM           120\n)?')
0535             self.assertRegex(call_kid3_cli(
0536                 ['-c', 'copy 3',
0537                  '-c', 'remove 3',
0538                  '-c', 'get all 3',
0539                  '-c', 'paste 3',
0540                  '-c', 'get all 3',
0541                  '-c', 'set tracknumber 6 3',
0542                  '-c', 'get all 3',
0543                  '-c', 'set comment "" 3',
0544                  '-c', 'get all 3',
0545                  '-c', 'remove 3', wavpath]),
0546                 'File: WAV (?:PCM 16 bit )?1411 kbps 44100 Hz 2 Channels\n'
0547                 '  Name: test\\.wav\n'
0548                 'File: WAV (?:PCM 16 bit )?1411 kbps 44100 Hz 2 Channels\n'
0549                 '  Name: test\\.wav\n'
0550                 '(?:Tag 3: RIFF INFO\n'
0551                 '\\* Artist        An Artist\n'
0552                 '\\* Album         An Album\n'
0553                 '\\* Comment       A Comment\n'
0554                 '\\* Date          2017\n'
0555                 '\\* Track Number  2\n'
0556                 '\\* Genre         Heavy Metal\n'
0557                 '\\* BPM           120\n)?'
0558                 'File: WAV (?:PCM 16 bit )?1411 kbps 44100 Hz 2 Channels\n'
0559                 '  Name: test\\.wav\n'
0560                 '(?:Tag 3: RIFF INFO\n'
0561                 '\\* Artist        An Artist\n'
0562                 '\\* Album         An Album\n'
0563                 '\\* Comment       A Comment\n'
0564                 '\\* Date          2017\n'
0565                 '\\* Track Number  6\n'
0566                 '\\* Genre         Heavy Metal\n'
0567                 '\\* BPM           120\n)?'
0568                 'File: WAV (?:PCM 16 bit )?1411 kbps 44100 Hz 2 Channels\n'
0569                 '  Name: test\\.wav\n'
0570                 '(?:Tag 3: RIFF INFO\n'
0571                 '\\* Artist        An Artist\n'
0572                 '\\* Album         An Album\n'
0573                 '\\* Date          2017\n'
0574                 '\\* Track Number  6\n'
0575                 '\\* Genre         Heavy Metal\n'
0576                 '\\* BPM           120\n)?')
0577             with open(wavpath, 'rb') as wavfh:
0578                 ba = wavfh.read()
0579                 self.assertEqual(ba, empty_wav_bytes)
0580 
0581     def test_multiple_files(self):
0582         with tempfile.TemporaryDirectory() as tmpdir:
0583             extensions = ('m4a', 'flac', 'spx', 'mp3', 'ape', 'wav', 'opus', 'aif')
0584             for nr, ext in enumerate(extensions):
0585                 create_test_file(os.path.join(tmpdir, 'track%02d.%s' % (nr, ext)))
0586             import_csv = \
0587                 '01\tWheels Of Fire\tManowar\tKings Of Metal\t1988\tMetal\t\t0:00.00\n' \
0588                 '02\tKings Of Metal\tManowar\tKings Of Metal\t1988\tMetal\t\t0:00.00\n' \
0589                 '03\tHeart Of Steel\tManowar\tKings Of Metal\t1988\tMetal\t\t0:00.00\n' \
0590                 '05\tThe Crown And The Ring (Lament Of The Kings)\tManowar\tKings Of Metal\t1988\tMetal\t\t0:00.00\n' \
0591                 '06\tKingdom Come\tManowar\tKings Of Metal\t1988\tMetal\t\t0:00.00\n' \
0592                 '08\tHail And Kill\tManowar\tKings Of Metal\t1988\tMetal\t\t0:00.00\n' \
0593                 '09\tThe Warriors Prayer\tManowar\tKings Of Metal\t1988\tMetal\t\t0:00.00\n' \
0594                 '10\tBlood Of The Kings\tManowar\tKings Of Metal\t1988\tMetal\t\t0:00.00\n'
0595             importpath = os.path.join(tmpdir, 'import.csv')
0596             exportpath = os.path.join(tmpdir, 'export.csv')
0597             with open(importpath, 'w') as importfh:
0598                 importfh.write(import_csv)
0599             self.assertEqual(call_kid3_cli(
0600                 ['-c', 'import "%s" "CSV unquoted"' % importpath,
0601                  '-c', 'export "%s" "CSV unquoted"' % exportpath, tmpdir]), '')
0602             with open(exportpath) as exportfh:
0603                 export_csv = exportfh.read()
0604                 self.assertEqual(export_csv, import_csv)
0605             os.remove(importpath)
0606             os.remove(exportpath)
0607             expected = (
0608                 'File: MP4 (?:AAC 16 bit )?9 kbps 44100 Hz 2 Channels\n'
0609                 '  Name: track00\\.m4a\n'
0610                 'Tag 2: MP4\n'
0611                 '  Title         Wheels Of Fire\n'
0612                 '  Artist        Manowar\n'
0613                 '  Album         Kings Of Metal\n'
0614                 '  Date          1988\n'
0615                 '  Track Number  1\n'
0616                 '  Genre         Metal\n'
0617                 'File: FLAC [^\\n]+ kbps 44100 Hz 1 Channels\n'
0618                 '  Name: track01\\.flac\n'
0619                 '(?:Tag 1: ID3v1\\.1\n'
0620                 '  Title         Kings Of Metal\n'
0621                 '  Artist        Manowar\n'
0622                 '  Album         Kings Of Metal\n'
0623                 '  Date          1988\n'
0624                 '  Track Number  02\n'
0625                 '  Genre         Metal\n)?'
0626                 'Tag 2: Vorbis\n'
0627                 '  Title         Kings Of Metal\n'
0628                 '  Artist        Manowar\n'
0629                 '  Album         Kings Of Metal\n'
0630                 '  Date          1988\n'
0631                 '  Track Number  02\n'
0632                 '  Genre         Metal\n'
0633                 'File: Speex 1 44100 Hz 1 Channels\n'
0634                 '  Name: track02\\.spx\n'
0635                 'Tag 2: Vorbis\n'
0636                 '  Title         Heart Of Steel\n'
0637                 '  Artist        Manowar\n'
0638                 '  Album         Kings Of Metal\n'
0639                 '  Date          1988\n'
0640                 '  Track Number  03\n'
0641                 '  Genre         Metal\n'
0642                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0643                 '  Name: track03\\.mp3\n'
0644                 'Tag 1: ID3v1\\.1\n'
0645                 '  Title         The Crown And The Ring \\(Lament\n'
0646                 '  Artist        Manowar\n'
0647                 '  Album         Kings Of Metal\n'
0648                 '  Date          1988\n'
0649                 '  Track Number  5\n'
0650                 '  Genre         Metal\n'
0651                 'Tag 2: ID3v2\\.3\\.0\n'
0652                 '  Title         The Crown And The Ring \\(Lament Of The Kings\\)\n'
0653                 '  Artist        Manowar\n'
0654                 '  Album         Kings Of Metal\n'
0655                 '  Date          1988\n'
0656                 '  Track Number  05\n'
0657                 '  Genre         Metal\n'
0658                 'File: APE 3\\.990 16 bit 44100 Hz 1 Channels\n'
0659                 '  Name: track04\\.ape\n'
0660                 'Tag 1: ID3v1\\.1\n'
0661                 '  Title         Kingdom Come\n'
0662                 '  Artist        Manowar\n'
0663                 '  Album         Kings Of Metal\n'
0664                 '  Date          1988\n'
0665                 '  Track Number  6\n'
0666                 '  Genre         Metal\n'
0667                 'Tag 2: APE\n'
0668                 '  Title         Kingdom Come\n'
0669                 '  Artist        Manowar\n'
0670                 '  Album         Kings Of Metal\n'
0671                 '  Date          1988\n'
0672                 '  Track Number  06\n'
0673                 '  Genre         Metal\n'
0674                 'File: WAV (?:PCM 16 bit )?1411 kbps 44100 Hz 2 Channels\n'
0675                 '  Name: track05\\.wav\n'
0676                 'Tag 2: ID3v2\\.3\\.0\n'
0677                 '  Title         Hail And Kill\n'
0678                 '  Artist        Manowar\n'
0679                 '  Album         Kings Of Metal\n'
0680                 '  Date          1988\n'
0681                 '  Track Number  08\n'
0682                 '  Genre         Metal\n'
0683                 'File: Opus 1 48000 Hz 1 Channels\n'
0684                 '  Name: track06\\.opus\n'
0685                 'Tag 2: Vorbis\n'
0686                 '  Title         The Warriors Prayer\n'
0687                 '  Artist        Manowar\n'
0688                 '  Album         Kings Of Metal\n'
0689                 '  Date          1988\n'
0690                 '  Track Number  09\n'
0691                 '  Genre         Metal\n'
0692                 'File: AIFF [^\\n]+ kbps 44100 Hz 2 Channels\n'
0693                 '  Name: track07\\.aif\n'
0694                 'Tag 2: ID3v2\\.4\\.0\n'
0695                 '  Title         Blood Of The Kings\n'
0696                 '  Artist        Manowar\n'
0697                 '  Album         Kings Of Metal\n'
0698                 '  Date          1988\n'
0699                 '  Track Number  10\n'
0700                 '  Genre         Metal\n'
0701                 'Tag 1:\n'
0702                 '  Title         ≠\n'
0703                 '  Artist        Manowar\n'
0704                 '  Album         Kings Of Metal\n'
0705                 '  Date          1988\n'
0706                 '  Track Number  ≠\n'
0707                 '  Genre         Metal\n'
0708                 'Tag 2:\n'
0709                 '  Title         ≠\n'
0710                 '  Artist        Manowar\n'
0711                 '  Album         Kings Of Metal\n'
0712                 '  Date          1988\n'
0713                 '  Track Number  ≠\n'
0714                 '  Genre         Metal\n')
0715             if sys.platform == 'win32':
0716                 expected = expected.replace('≠', '\\?')
0717             self.assertRegex(call_kid3_cli(
0718                 ['-c', 'select first',
0719                  '-c', 'get',
0720                  '-c', 'select next',
0721                  '-c', 'get',
0722                  '-c', 'select next',
0723                  '-c', 'get',
0724                  '-c', 'select next',
0725                  '-c', 'get',
0726                  '-c', 'select next',
0727                  '-c', 'get',
0728                  '-c', 'select next',
0729                  '-c', 'get',
0730                  '-c', 'select next',
0731                  '-c', 'get',
0732                  '-c', 'select next',
0733                  '-c', 'get',
0734                  '-c', 'select all',
0735                  '-c', 'get', tmpdir]),
0736                 expected)
0737             call_kid3_cli(['-c', 'fromtag "%{track} %{title}" 2', os.path.join(tmpdir, '*.*')])
0738             self.assertRegex(call_kid3_cli(
0739                 ['-c', 'ls', tmpdir]),
0740                 '  -2- 01 Wheels Of Fire.m4a\n'
0741                 '  [-1]2- 02 Kings Of Metal.flac\n'
0742                 '  -2- 03 Heart Of Steel.spx\n'
0743                 '  12- 05 The Crown And The Ring \\(Lament Of The Kings\\).mp3\n'
0744                 '  12- 06 Kingdom Come.ape\n'
0745                 '  -2- 08 Hail And Kill.wav\n'
0746                 '  -2- 09 The Warriors Prayer.opus\n'
0747                 '  -2- 10 Blood Of The Kings.aif\n')
0748             self.assertEqual(call_kid3_cli(
0749                 ['-c', 'tag 2',
0750                  '-c', 'numbertracks 101',
0751                  '-c', 'select first',
0752                  '-c', 'get track',
0753                  '-c', 'select next',
0754                  '-c', 'get track',
0755                  '-c', 'select next',
0756                  '-c', 'get track',
0757                  '-c', 'select next',
0758                  '-c', 'get track',
0759                  '-c', 'select next',
0760                  '-c', 'get track',
0761                  '-c', 'select next',
0762                  '-c', 'get track',
0763                  '-c', 'select next',
0764                  '-c', 'get track',
0765                  '-c', 'select next',
0766                  '-c', 'get track',
0767                  '-c', 'revert', tmpdir
0768                  ]),
0769                 '101\n102\n103\n104\n105\n106\n107\n108\n')
0770             lines = call_kid3_cli(
0771                 ['-c', 'filter "%{tag2} equals ID3v2.3.0"', tmpdir]).splitlines()
0772             self.assertEqual(lines[0], 'Started')
0773             self.assertTrue(os.path.samefile(lines[1].lstrip(), tmpdir))
0774             self.assertEqual(
0775                 lines[2:],
0776                 ['- 01 Wheels Of Fire.m4a', '- 02 Kings Of Metal.flac',
0777                  '- 03 Heart Of Steel.spx', '+ 05 The Crown And The Ring (Lament Of The Kings).mp3',
0778                  '- 06 Kingdom Come.ape', '+ 08 Hail And Kill.wav', '- 09 The Warriors Prayer.opus',
0779                  '- 10 Blood Of The Kings.aif', 'Finished'])
0780             call_kid3_cli(['-c', 'renamedir "%{artist} - [%{year}] %{album}" "create"', tmpdir])
0781             new_dirname = 'Manowar - [1988] Kings Of Metal'
0782             new_dirpath = os.path.join(tmpdir, new_dirname)
0783             self.assertEqual(os.listdir(tmpdir)[0], new_dirname)
0784             call_kid3_cli(['-c', 'playlist', new_dirpath])
0785             with open(os.path.join(new_dirpath, new_dirname + '.m3u')) as m3ufh:
0786                 export_m3u = m3ufh.read()
0787             self.assertEqual(
0788                 export_m3u,
0789                 '01 Wheels Of Fire.m4a\n'
0790                 '02 Kings Of Metal.flac\n'
0791                 '03 Heart Of Steel.spx\n'
0792                 '05 The Crown And The Ring (Lament Of The Kings).mp3\n'
0793                 '06 Kingdom Come.ape\n'
0794                 '08 Hail And Kill.wav\n'
0795                 '09 The Warriors Prayer.opus\n'
0796                 '10 Blood Of The Kings.aif\n')
0797 
0798     def test_filename_tag_format(self):
0799         with tempfile.TemporaryDirectory() as tmpdir:
0800             albumdir = os.path.join(tmpdir, 'An Artist - 2016 - An Album')
0801             os.mkdir(albumdir)
0802             create_test_file(os.path.join(albumdir, '01. A title.mp3'))
0803             self.assertEqual(call_kid3_cli(
0804                 ['-c', 'totag "%{artist} - %{date} - %{album}/%{track}. %{title}" 2',
0805                  '-c', 'save',
0806                  '-c', 'get', os.path.join(albumdir, '*.mp3')]),
0807                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0808                 '  Name: 01. A title.mp3\n'
0809                 'Tag 2: ID3v2.3.0\n'
0810                 '  Title         A title\n'
0811                 '  Artist        An Artist\n'
0812                 '  Album         An Album\n'
0813                 '  Date          2016\n'
0814                 '  Track Number  1\n')
0815             self.assertEqual(call_kid3_cli(
0816                 ['-c', 'select all',
0817                  '-c', 'tagformat',
0818                  '-c', 'save',
0819                  '-c', 'get all', albumdir]),
0820                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0821                 '  Name: 01. A title.mp3\n'
0822                 'Tag 2: ID3v2.3.0\n'
0823                 '  Title         A Title\n'
0824                 '  Artist        An Artist\n'
0825                 '  Album         An Album\n'
0826                 '  Date          2016\n'
0827                 '  Track Number  1\n')
0828             expected = (
0829                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0830                 '* Name: 01. Schön.mp3\n'
0831                 'Tag 2: ID3v2.3.0\n'
0832                 '* Title         Schön\n'
0833                 '  Artist        An Artist\n'
0834                 '  Album         An Album\n'
0835                 '  Date          2016\n'
0836                 '  Track Number  1\n'
0837             )
0838             if sys.platform == 'win32' and platform.release() == 'XP':
0839                 expected = expected.replace('ö', '?')
0840             self.assertEqual(call_kid3_cli(
0841                 ['-c', 'select first',
0842                  '-c', 'set title "Schön"',
0843                  '-c', 'fromtag "%{track}. %{title}" 2',
0844                  '-c', 'get',
0845                  '-c', 'filenameformat', albumdir]),
0846                 expected)
0847             expected = (
0848                 'File: MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels\n'
0849                 '  Name: 01 Schoen.mp3\n'
0850                 'Tag 2: ID3v2.3.0\n'
0851                 '  Title         Schön\n'
0852                 '  Artist        An Artist\n'
0853                 '  Album         An Album\n'
0854                 '  Date          2016\n'
0855                 '  Track Number  1\n'
0856             )
0857             if sys.platform == 'win32' and platform.release() == 'XP':
0858                 expected = expected.replace('ö', '?')
0859             self.assertEqual(call_kid3_cli(
0860                 ['-c', 'get', os.path.join(albumdir, '*.mp3')]),
0861                 expected)
0862 
0863 
0864 class CliFunctionsJsonTestCase(unittest.TestCase):
0865     def test_invalid(self):
0866         for cmd, rsp in (
0867                 ('{"abc":"def"}',
0868                  {'error': {'code': -1,
0869                             'message': 'missing method: {"abc":"def"}'}}),
0870                 ('{"method":"unknown"}',
0871                  {'error': {'code': -1,
0872                             'message': 'Unknown command '
0873                             '\'{"method":"unknown"}\', -h for help.'}}),
0874                 ('{"method":"timeout",params:["off"]}',
0875                  {'error': {'code': -1,
0876                             'message': 'unterminated object: '
0877                             '{"method":"timeout",params:["off"]}'}}),
0878                 ('{"method":"timeout","params":["default"}',
0879                  {'error': {'code': -1,
0880                             'message': 'unterminated array: '
0881                             '{"method":"timeout","params":["default"}'}}),
0882                 ('{"method":"unknown"}',
0883                  {'error': {'code': -1,
0884                             'message': 'Unknown command '
0885                             '\'{"method":"unknown"}\', -h for help.'}}),
0886         ):
0887             p = subprocess.Popen([kid3_cli_path(), '-c', cmd],
0888                             stdout=subprocess.PIPE, stderr=subprocess.PIPE,
0889                             universal_newlines=True)
0890             stdout, stderr = p.communicate()
0891             result = json.loads(stdout)
0892             self.assertEqual(result, rsp)
0893             self.assertEqual(p.returncode, 1)
0894 
0895     def test_timeout(self):
0896         self.assertEqual(call_kid3_cli(
0897             ['-c', '{"method":"timeout"}',
0898              '-c', '{"method":"timeout","params":[5000]}',
0899              '-c', '{"method":"timeout"}',
0900              '-c', '{"method":"timeout","params":["off"]}',
0901              '-c', '{"method":"timeout"}',
0902              '-c', '{"method":"timeout","params":["default"]}',
0903              '-c', '{"method":"timeout"}']),
0904             '{"result":{"timeout":"default"}}\n'
0905             '{"result":{"timeout":"5000 ms"}}\n'
0906             '{"result":{"timeout":"5000 ms"}}\n'
0907             '{"result":{"timeout":"off"}}\n'
0908             '{"result":{"timeout":"off"}}\n'
0909             '{"result":{"timeout":"default"}}\n'
0910             '{"result":{"timeout":"default"}}\n')
0911 
0912     def test_json_methods(self):
0913         self.maxDiff=None
0914         with tempfile.TemporaryDirectory() as tmpdir:
0915             mp3path = os.path.join(tmpdir, 'test.mp3')
0916             create_test_file(mp3path)
0917             self.assertEqual(call_kid3_cli(
0918                 ['-c', '{"method":"get","params":["title", 2]}',
0919                  '-c', '{"method":"get","params":["all",[1,2]]}',
0920                  '-c', '{"method":"set","params":["title","A Title"]}',
0921                  '-c', '{"method":"get","params":["title"]}',
0922                  '-c', '{"method": "get", "params": ["title"]}',
0923                  '-c', '{"method":"get"}',
0924                  '-c', '{"method":"select","params":["none"]}',
0925                  '-c', '{"method":"ls"}',
0926                  '-c', '{"method":"select","params":["first"]}',
0927                  '-c', '{"method":"filenameformat"}',
0928                  '-c', '{"method":"tagformat"}',
0929                  '-c', '{"method":"textencoding"}',
0930                  '-c', '{"method":"numbertracks","params":[3,2]}',
0931                  '-c', '{"jsonrpc":"2.0","id":"1","method":"remove",'
0932                          '"params":[1]}',
0933                  '-c', '{"method":"ls"}',
0934                 mp3path]),
0935                 '{"result":null}\n'
0936                 '{"result":{"taggedFile":{"fileName":"test.mp3",'
0937                   '"fileNameChanged":false,"format":'
0938                   '"MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels"}}}\n'
0939                 '{"result":null}\n'
0940                 '{"result":"A Title"}\n'
0941                 '{\n'
0942                 '    "result": "A Title"\n'
0943                 '}\n\n'
0944                 '{"result":{"taggedFile":{"fileName":"test.mp3",'
0945                   '"fileNameChanged":false,"format":'
0946                   '"MPEG 1 Layer 3 64 kbps 44100 Hz 1 Channels",'
0947                   '"tag2":{"format":"ID3v2.3.0","frames":[{"changed":true,'
0948                   '"name":"Title","value":"A Title"}]}}}}\n'
0949                 '{"result":null}\n'
0950                 '{"result":{"files":[{"changed":true,"fileName":"test.mp3",'
0951                   '"selected":false,"tags":[2]}]}}\n'
0952                 '{"result":null}\n'
0953                 '{"result":null}\n'
0954                 '{"result":null}\n'
0955                 '{"result":null}\n'
0956                 '{"result":null}\n'
0957                 '{"id":"1","jsonrpc":"2.0","result":null}\n'
0958                 '{"result":{"files":[{"changed":true,"fileName":"test.mp3",'
0959                   '"selected":true,"tags":[2]}]}}\n')
0960 
0961 
0962 if __name__ == '__main__':
0963     unittest.main()