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.
@@ -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!
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,9 @@
1
+ README.md
2
+ setup.py
3
+ video2psp/__init__.py
4
+ video2psp/psp.py
5
+ video2psp.egg-info/PKG-INFO
6
+ video2psp.egg-info/SOURCES.txt
7
+ video2psp.egg-info/dependency_links.txt
8
+ video2psp.egg-info/entry_points.txt
9
+ video2psp.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ video2psp = video2psp.psp:main
@@ -0,0 +1 @@
1
+ video2psp