video2psp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- video2psp-0.1.0/PKG-INFO +8 -0
- video2psp-0.1.0/README.md +43 -0
- video2psp-0.1.0/setup.cfg +4 -0
- video2psp-0.1.0/setup.py +18 -0
- video2psp-0.1.0/video2psp/__init__.py +0 -0
- video2psp-0.1.0/video2psp/psp.py +292 -0
- video2psp-0.1.0/video2psp.egg-info/PKG-INFO +8 -0
- video2psp-0.1.0/video2psp.egg-info/SOURCES.txt +9 -0
- video2psp-0.1.0/video2psp.egg-info/dependency_links.txt +1 -0
- video2psp-0.1.0/video2psp.egg-info/entry_points.txt +2 -0
- video2psp-0.1.0/video2psp.egg-info/top_level.txt +1 -0
video2psp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: video2psp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A CLI tool to convert video to PSP MP4 with user-selected audio and subtitle tracks.
|
|
5
|
+
Home-page: https://github.com/ghurone/video2psp
|
|
6
|
+
Author: Erick Ghuron
|
|
7
|
+
Author-email: ghuron@usp.br
|
|
8
|
+
Requires-Python: >=3.11
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# video2psp
|
|
2
|
+
|
|
3
|
+
**video2psp** is a Python CLI tool that converts videos to a PSP-compatible MP4 format, allowing you to select which audio and subtitle track to include (burned in) or optionally use an external subtitle file.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
1. Make sure you have [FFmpeg](https://ffmpeg.org/) (including `ffprobe`) installed and accessible in your system’s PATH.
|
|
8
|
+
2. Clone or download this repository and run:
|
|
9
|
+
```bash
|
|
10
|
+
pip install .
|
|
11
|
+
```
|
|
12
|
+
This will install **video2psp** as a console command.
|
|
13
|
+
|
|
14
|
+
Or install using PyPI
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install video2psp
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
With the tool installed, simply run:
|
|
23
|
+
```bash
|
|
24
|
+
video2psp <input_video> <output_video> [options]
|
|
25
|
+
```
|
|
26
|
+
- If you do **not** specify `--audio-track` or `--subtitle-track`, the script will prompt you to pick which tracks to use.
|
|
27
|
+
- To skip prompts, you can set them explicitly, for example:
|
|
28
|
+
```bash
|
|
29
|
+
video2psp movie.mkv out.mp4 --audio-track 1 --subtitle-track 2
|
|
30
|
+
```
|
|
31
|
+
- To burn an external subtitle file instead of an embedded track:
|
|
32
|
+
```bash
|
|
33
|
+
video2psp movie.mkv out.mp4 --external-subs subs.srt
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Notes & Limitations
|
|
37
|
+
|
|
38
|
+
- The **PSP** typically supports H.264 Baseline Level 3.0 with AAC audio.
|
|
39
|
+
- Resolution is scaled to **480 pixels wide** (height adjusted automatically) to match PSP screen specs.
|
|
40
|
+
- Only **one** audio track is included, and **one** subtitle track can be burned.
|
|
41
|
+
- For image-based subtitles (like PGS or VobSub), you need to convert them to a text format (e.g., SRT) before burning.
|
|
42
|
+
|
|
43
|
+
Feel free to contribute or customize further. Enjoy converting videos for your PSP!
|
video2psp-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# setup.py
|
|
2
|
+
from setuptools import setup, find_packages
|
|
3
|
+
|
|
4
|
+
setup(
|
|
5
|
+
name="video2psp",
|
|
6
|
+
version="0.1.0",
|
|
7
|
+
packages=find_packages(),
|
|
8
|
+
url="https://github.com/ghurone/video2psp",
|
|
9
|
+
entry_points={
|
|
10
|
+
"console_scripts": [
|
|
11
|
+
"video2psp=video2psp.psp:main",
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
author="Erick Ghuron",
|
|
15
|
+
author_email="ghuron@usp.br",
|
|
16
|
+
description="A CLI tool to convert video to PSP MP4 with user-selected audio and subtitle tracks.",
|
|
17
|
+
python_requires=">=3.11",
|
|
18
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import subprocess
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def ffprobe_streams(input_file:str) -> list:
|
|
9
|
+
"""
|
|
10
|
+
Return the streams information for the given file, as reported by ffprobe
|
|
11
|
+
(JSON).
|
|
12
|
+
"""
|
|
13
|
+
probe_cmd = [
|
|
14
|
+
'ffprobe',
|
|
15
|
+
'-v', 'quiet',
|
|
16
|
+
'-print_format', 'json',
|
|
17
|
+
'-show_streams',
|
|
18
|
+
input_file
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
result = subprocess.run(
|
|
23
|
+
probe_cmd,
|
|
24
|
+
stdout=subprocess.PIPE,
|
|
25
|
+
stderr=subprocess.PIPE,
|
|
26
|
+
check=True
|
|
27
|
+
)
|
|
28
|
+
output = result.stdout.decode('utf-8', errors='replace')
|
|
29
|
+
return json.loads(output).get('streams', [])
|
|
30
|
+
|
|
31
|
+
except FileNotFoundError:
|
|
32
|
+
print("Error: 'ffprobe' not found. Make sure FFmpeg (with ffprobe) "
|
|
33
|
+
"is installed and in PATH.")
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
except subprocess.CalledProcessError as e:
|
|
36
|
+
print(f"Error: ffprobe returned a non-zero exit status.\n{e}")
|
|
37
|
+
print("stderr:", e.stderr.decode('utf-8', errors='replace'))
|
|
38
|
+
sys.exit(1)
|
|
39
|
+
except json.JSONDecodeError:
|
|
40
|
+
print("Error: Could not parse ffprobe output as JSON.")
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def list_tracks(input_file: str) -> tuple[list, list]:
|
|
45
|
+
"""
|
|
46
|
+
Return two lists: (audio_tracks, subtitle_tracks).
|
|
47
|
+
Each is a list of dicts with keys: ff_index (int), codec, language, title.
|
|
48
|
+
"""
|
|
49
|
+
streams = ffprobe_streams(input_file)
|
|
50
|
+
|
|
51
|
+
audio_tracks = []
|
|
52
|
+
subtitle_tracks = []
|
|
53
|
+
|
|
54
|
+
for s in streams:
|
|
55
|
+
ff_index = s.get('index')
|
|
56
|
+
codec_type = s.get('codec_type')
|
|
57
|
+
codec_name = s.get('codec_name', 'unknown')
|
|
58
|
+
tags = s.get('tags', {})
|
|
59
|
+
language = tags.get('language', 'und') # 'und' = undefined
|
|
60
|
+
title = tags.get('title', '')
|
|
61
|
+
|
|
62
|
+
if codec_type == 'audio':
|
|
63
|
+
audio_tracks.append({
|
|
64
|
+
'ff_index': ff_index,
|
|
65
|
+
'codec': codec_name,
|
|
66
|
+
'language': language,
|
|
67
|
+
'title': title
|
|
68
|
+
})
|
|
69
|
+
elif codec_type == 'subtitle':
|
|
70
|
+
subtitle_tracks.append({
|
|
71
|
+
'ff_index': ff_index,
|
|
72
|
+
'codec': codec_name,
|
|
73
|
+
'language': language,
|
|
74
|
+
'title': title
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
return audio_tracks, subtitle_tracks
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def build_ffmpeg_command(
|
|
81
|
+
input_file:str,
|
|
82
|
+
output_file:str,
|
|
83
|
+
audio_track_index:str,
|
|
84
|
+
subtitle_track_index:str = None,
|
|
85
|
+
external_subs:str = None
|
|
86
|
+
|
|
87
|
+
) -> list[str]:
|
|
88
|
+
"""
|
|
89
|
+
Build an ffmpeg command for PSP-compatible MP4 with chosen audio track
|
|
90
|
+
(re-encoded), and optionally burn in a chosen subtitle track or external
|
|
91
|
+
subtitle file.
|
|
92
|
+
|
|
93
|
+
- audio_track_index (int): Ex: 1 significa '0:1' no ffmpeg.
|
|
94
|
+
- subtitle_track_index (int): Ex: 2 significa '0:2', ou None para não usar.
|
|
95
|
+
- external_subs (str): caminho para .srt/.ass externo; se setado,
|
|
96
|
+
ignora legendas embutidas.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
cmd = [
|
|
100
|
+
'ffmpeg',
|
|
101
|
+
'-y', # Overwrite sem perguntar
|
|
102
|
+
'-i', input_file
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
# Mapeia o primeiro track de vídeo (geralmente 0:v:0)
|
|
106
|
+
cmd += ['-map', '0:v:0']
|
|
107
|
+
# Mapeia o track de áudio escolhido (0:a:<índiceGlobal>)
|
|
108
|
+
cmd += ['-map', f'0:{audio_track_index}']
|
|
109
|
+
|
|
110
|
+
# Filtro base para escalonar para resolução do PSP (largura=480, altura ajustada)
|
|
111
|
+
base_vf = "scale=480:-2"
|
|
112
|
+
|
|
113
|
+
vf_filter = base_vf # padrão (sem legendas)
|
|
114
|
+
|
|
115
|
+
if external_subs:
|
|
116
|
+
# Legenda externa
|
|
117
|
+
vf_filter = f"{base_vf},subtitles='{external_subs}'"
|
|
118
|
+
elif subtitle_track_index is not None:
|
|
119
|
+
audio_tracks, subtitle_tracks = list_tracks(input_file)
|
|
120
|
+
|
|
121
|
+
sub_order = None
|
|
122
|
+
for i, st in enumerate(subtitle_tracks):
|
|
123
|
+
if st['ff_index'] == subtitle_track_index:
|
|
124
|
+
sub_order = i
|
|
125
|
+
break
|
|
126
|
+
|
|
127
|
+
if sub_order is None:
|
|
128
|
+
print("Warning: Could not find the specified subtitle track "
|
|
129
|
+
"among embedded subtitles.")
|
|
130
|
+
print("Skipping subtitle burn-in.")
|
|
131
|
+
else:
|
|
132
|
+
vf_filter = f"{base_vf},subtitles='{input_file}:si={sub_order}'"
|
|
133
|
+
|
|
134
|
+
cmd += ['-vf', vf_filter]
|
|
135
|
+
|
|
136
|
+
# Configurações de encoding para PSP (H.264 baseline, ~768kbps, AAC 128kbps)
|
|
137
|
+
cmd += [
|
|
138
|
+
'-c:v', 'libx264',
|
|
139
|
+
'-profile:v', 'baseline',
|
|
140
|
+
'-level:v', '3.0',
|
|
141
|
+
'-b:v', '768k',
|
|
142
|
+
'-maxrate', '768k',
|
|
143
|
+
'-bufsize', '2000k',
|
|
144
|
+
'-r', '29.97', # framerate PSP comum
|
|
145
|
+
'-c:a', 'aac',
|
|
146
|
+
'-b:a', '128k',
|
|
147
|
+
'-ac', '2', # stereo
|
|
148
|
+
output_file
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
return cmd
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def main():
|
|
155
|
+
parser = argparse.ArgumentParser(
|
|
156
|
+
description="Convert video to PSP MP4 with user-selected audio and "
|
|
157
|
+
"subtitle tracks."
|
|
158
|
+
)
|
|
159
|
+
parser.add_argument(
|
|
160
|
+
"input_file",
|
|
161
|
+
type=str,
|
|
162
|
+
help="Path to input video."
|
|
163
|
+
)
|
|
164
|
+
parser.add_argument(
|
|
165
|
+
"output_file",
|
|
166
|
+
type=str,
|
|
167
|
+
default=None,
|
|
168
|
+
help="Path to output PSP-compatible MP4."
|
|
169
|
+
)
|
|
170
|
+
parser.add_argument(
|
|
171
|
+
"--audio-track",
|
|
172
|
+
type=int,
|
|
173
|
+
default=None,
|
|
174
|
+
help="Global ffmpeg index of the audio track you want to use "
|
|
175
|
+
"(e.g. 1 for '0:1')."
|
|
176
|
+
)
|
|
177
|
+
parser.add_argument(
|
|
178
|
+
"--subtitle-track",
|
|
179
|
+
type=int,
|
|
180
|
+
default=None,
|
|
181
|
+
help="Global ffmpeg index of an embedded subtitle track to burn in "
|
|
182
|
+
"(e.g. 2 for '0:2')."
|
|
183
|
+
)
|
|
184
|
+
parser.add_argument(
|
|
185
|
+
"--external-subs",
|
|
186
|
+
help="Path to an external .srt/.ass file for burning into the video "
|
|
187
|
+
"(overrides embedded).",
|
|
188
|
+
default=None,
|
|
189
|
+
type=str
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
args = parser.parse_args()
|
|
193
|
+
|
|
194
|
+
input_file = args.input_file
|
|
195
|
+
base, _ = os.path.splitext(input_file)
|
|
196
|
+
output_file = args.output_file if args.output_file else f"{base}.mp4"
|
|
197
|
+
audio_track_index = args.audio_track
|
|
198
|
+
subtitle_track_index = args.subtitle_track
|
|
199
|
+
external_subs = args.external_subs
|
|
200
|
+
|
|
201
|
+
audio_tracks, subtitle_tracks = list_tracks(input_file)
|
|
202
|
+
|
|
203
|
+
if not audio_tracks:
|
|
204
|
+
print("Error: No audio tracks found in the input file. Exiting.")
|
|
205
|
+
sys.exit(1)
|
|
206
|
+
|
|
207
|
+
if audio_track_index is None:
|
|
208
|
+
if len(audio_tracks) == 1:
|
|
209
|
+
print('Detected 1 AUDIO track!')
|
|
210
|
+
audio_track_index = audio_tracks[0]['ff_index']
|
|
211
|
+
else:
|
|
212
|
+
print(f"Detected ({len(audio_tracks)}) the following AUDIO tracks:")
|
|
213
|
+
for i, t in enumerate(audio_tracks):
|
|
214
|
+
print(
|
|
215
|
+
f" [{i}] ff_index=0:{t['ff_index']}, "
|
|
216
|
+
f"codec={t['codec']}, "
|
|
217
|
+
f"lang={t['language']}, "
|
|
218
|
+
f"title={t['title']}"
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
while True:
|
|
222
|
+
user_choice = input(
|
|
223
|
+
f"Select which audio track (0 to {len(audio_tracks)-1}): "
|
|
224
|
+
)
|
|
225
|
+
try:
|
|
226
|
+
user_choice_int = int(user_choice)
|
|
227
|
+
if 0 <= user_choice_int < len(audio_tracks):
|
|
228
|
+
audio_track_index = audio_tracks[user_choice_int]['ff_index']
|
|
229
|
+
break
|
|
230
|
+
else:
|
|
231
|
+
print("Invalid choice. Try again.")
|
|
232
|
+
except ValueError:
|
|
233
|
+
print("Invalid input. Try again.")
|
|
234
|
+
|
|
235
|
+
if external_subs is None and subtitle_track_index is None:
|
|
236
|
+
if subtitle_tracks:
|
|
237
|
+
print("\nDetected the following SUBTITLE tracks:")
|
|
238
|
+
for i, st in enumerate(subtitle_tracks):
|
|
239
|
+
print(
|
|
240
|
+
f" [{i}] ff_index=0:{st['ff_index']}, "
|
|
241
|
+
f"codec={st['codec']}, lang={st['language']}, "
|
|
242
|
+
f"title={st['title']}"
|
|
243
|
+
)
|
|
244
|
+
print("Enter -1 (or leave blank) if you do not want to burn any "
|
|
245
|
+
"embedded subtitles.")
|
|
246
|
+
|
|
247
|
+
while True:
|
|
248
|
+
user_sub_choice = input(
|
|
249
|
+
f"Select which subtitle track (0 to {len(subtitle_tracks)-1} "
|
|
250
|
+
f"or -1): "
|
|
251
|
+
)
|
|
252
|
+
if user_sub_choice.strip() == '':
|
|
253
|
+
user_sub_choice = '-1'
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
user_sub_choice_int = int(user_sub_choice)
|
|
257
|
+
if user_sub_choice_int == -1:
|
|
258
|
+
subtitle_track_index = None
|
|
259
|
+
break
|
|
260
|
+
elif 0 <= user_sub_choice_int < len(subtitle_tracks):
|
|
261
|
+
subtitle_track_index = (
|
|
262
|
+
subtitle_tracks[user_sub_choice_int]['ff_index']
|
|
263
|
+
)
|
|
264
|
+
break
|
|
265
|
+
else:
|
|
266
|
+
print("Invalid choice. Try again.")
|
|
267
|
+
except ValueError:
|
|
268
|
+
print("Invalid input. Try again.")
|
|
269
|
+
else:
|
|
270
|
+
print("\nNo embedded subtitle tracks found.")
|
|
271
|
+
|
|
272
|
+
cmd = build_ffmpeg_command(
|
|
273
|
+
input_file=input_file,
|
|
274
|
+
output_file=output_file,
|
|
275
|
+
audio_track_index=audio_track_index,
|
|
276
|
+
subtitle_track_index=subtitle_track_index,
|
|
277
|
+
external_subs=external_subs
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
print("\nRunning FFmpeg:")
|
|
281
|
+
print(" ".join(map(str, cmd)))
|
|
282
|
+
|
|
283
|
+
try:
|
|
284
|
+
subprocess.run(cmd, check=True)
|
|
285
|
+
print(f"\nSuccessfully created PSP video: {output_file}")
|
|
286
|
+
except subprocess.CalledProcessError as e:
|
|
287
|
+
print("\nError: FFmpeg command failed.")
|
|
288
|
+
print(e)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
if __name__ == "__main__":
|
|
292
|
+
main()
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: video2psp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A CLI tool to convert video to PSP MP4 with user-selected audio and subtitle tracks.
|
|
5
|
+
Home-page: https://github.com/ghurone/video2psp
|
|
6
|
+
Author: Erick Ghuron
|
|
7
|
+
Author-email: ghuron@usp.br
|
|
8
|
+
Requires-Python: >=3.11
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
video2psp
|